You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Redux状态结构优化咨询:React Native应用如何规范存储状态?

How to Implement Normalized Redux State for React Native Apps

Hey Zach, great question—this is such a common pivot when you start scaling Redux apps, and you’re totally right that ditching UI-centric state for normalized data will save you tons of headaches with duplicate data and sync issues. Let’s break down exactly how to implement this step by step, with concrete examples.

Core Concept First

Normalizing your Redux state means structuring it like a relational database:

  • Each data entity (e.g., User, Post, Order) gets its own "table" (reducer).
  • Entities are stored as an object where keys are entity IDs, and values are the full entity objects.
  • Relationships between entities are handled by storing IDs (not full objects) as references.
  • UI-specific state (like loading spinners, active tabs) is kept separate from your core data.

Step-by-Step Implementation

1. Map Out Your Core Entities

First, list all the distinct data types in your app. For example:

  • Users
  • Posts
  • Comments
  • Products
  • Orders

These are your "top-level" state slices—each will have its own reducer.

2. Restructure State Slices to Be Normalized

Instead of nesting data under UI keys (like homeScreen.posts), each entity slice follows this pattern:

{
  byId: { /* key: entity ID, value: entity object */ },
  allIds: [ /* array of all entity IDs, for easy iteration */ ]
}

Example: Before vs. After

Old (UI-centric) state:

{
  homeScreen: {
    posts: [
      { id: 1, title: "My First Post", author: { id: 101, name: "Zach" } },
      { id: 2, title: "Another Post", author: { id: 101, name: "Zach" } }
    ],
    loading: false
  },
  profileScreen: {
    user: { id: 101, name: "Zach" },
    posts: [ /* duplicate post objects here */ ]
  }
}

New (normalized) state:

{
  users: {
    byId: { 101: { id: 101, name: "Zach" } },
    allIds: [101]
  },
  posts: {
    byId: {
      1: { id: 1, title: "My First Post", authorId: 101 },
      2: { id: 2, title: "Another Post", authorId: 101 }
    },
    allIds: [1, 2]
  },
  ui: { // Separate UI state from data
    homeScreen: { loading: false },
    profileScreen: { activeTab: "posts" }
  }
}

3. Update Reducers to Handle Normalized Data

Each entity reducer only manages its own slice. For example, a postsReducer:

const initialState = {
  byId: {},
  allIds: []
};

function postsReducer(state = initialState, action) {
  switch (action.type) {
    case 'FETCH_POSTS_SUCCESS':
      // Convert array of posts to normalized structure
      const newById = { ...state.byId };
      const newAllIds = [...state.allIds];
      
      action.payload.forEach(post => {
        if (!newById[post.id]) {
          newById[post.id] = post;
          newAllIds.push(post.id);
        }
      });
      
      return { byId: newById, allIds: newAllIds };

    case 'UPDATE_POST_SUCCESS':
      // Update a single post without touching other data
      return {
        ...state,
        byId: {
          ...state.byId,
          [action.payload.id]: action.payload
        }
      };

    default:
      return state;
  }
}

4. Use Selectors to Assemble UI-Friendly Data

Since your raw state is normalized, you’ll need selectors to combine related data for your components. Use createSelector (from Redux Toolkit or reselect) to memoize these calculations and avoid unnecessary re-renders.

Example Selector for Home Screen Posts + Authors

import { createSelector } from '@reduxjs/toolkit';

// Base selectors to get raw state slices
const selectPostsById = state => state.posts.byId;
const selectAllPostIds = state => state.posts.allIds;
const selectUsersById = state => state.users.byId;

// Memoized selector to combine posts with their authors
export const selectHomePostsWithAuthors = createSelector(
  [selectAllPostIds, selectPostsById, selectUsersById],
  (postIds, posts, users) => {
    return postIds.map(postId => ({
      ...posts[postId],
      author: users[posts[postId].authorId]
    }));
  }
);

5. Refactor Actions to Be Entity-Focused

Instead of UI-specific actions like HOME_SCREEN_POSTS_LOADED, use entity-focused actions like FETCH_POSTS_SUCCESS. This way, any component that needs posts will trigger the same state update, ensuring consistency across your app.

Pro Tips to Simplify Implementation

  • Use Redux Toolkit’s createEntityAdapter: This utility abstracts away most of the boilerplate for normalized state. It provides built-in methods like addOne, addMany, updateOne, and pre-made selectors. It’s a huge time-saver.
  • Refactor Gradually: Don’t rewrite your entire state at once. Pick one entity (like Posts) to normalize first, test it thoroughly, then move on to the next. This reduces risk and lets you learn as you go.
  • Keep UI State Separate: Always store things like loading states, modal visibility, or active tabs in a dedicated ui slice—never mix them with your core data entities.

内容的提问来源于stack exchange,提问作者Zach

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:57:36