NativeScript 状态管理 #

状态管理概述 #

状态管理是管理应用数据流的核心,良好的状态管理能让应用更易维护和扩展。

text
┌─────────────────────────────────────────────────────────────┐
│                    状态管理方案                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  基础方案                                                    │
│  ├── Observable         NativeScript 内置                   │
│  └── Service + Subject  Angular 服务                        │
│                                                             │
│  集中式状态管理                                              │
│  ├── NgRx               Angular Redux                       │
│  ├── Vuex               Vue 状态管理                        │
│  └── Redux              React 状态管理                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Observable 状态管理 #

基本状态管理 #

typescript
// viewmodels/app.viewmodel.ts
import { Observable, fromObject } from '@nativescript/core';

export class AppViewModel extends Observable {
    private _user: User | null = null;
    private _isLoggedIn: boolean = false;
    private _isLoading: boolean = false;
    
    get user(): User | null {
        return this._user;
    }
    
    set user(value: User | null) {
        if (this._user !== value) {
            this._user = value;
            this.notifyPropertyChange('user', value);
        }
    }
    
    get isLoggedIn(): boolean {
        return this._isLoggedIn;
    }
    
    set isLoggedIn(value: boolean) {
        if (this._isLoggedIn !== value) {
            this._isLoggedIn = value;
            this.notifyPropertyChange('isLoggedIn', value);
        }
    }
    
    get isLoading(): boolean {
        return this._isLoading;
    }
    
    set isLoading(value: boolean) {
        if (this._isLoading !== value) {
            this._isLoading = value;
            this.notifyPropertyChange('isLoading', value);
        }
    }
    
    async login(email: string, password: string): Promise<void> {
        this.isLoading = true;
        
        try {
            const user = await authService.login(email, password);
            this.user = user;
            this.isLoggedIn = true;
        } finally {
            this.isLoading = false;
        }
    }
    
    logout(): void {
        this.user = null;
        this.isLoggedIn = false;
    }
}

全局状态服务 #

typescript
// services/state.service.ts
import { Injectable } from '@angular/core';
import { Observable } from '@nativescript/core';

interface AppState {
    user: User | null;
    isLoggedIn: boolean;
    theme: 'light' | 'dark';
}

@Injectable({
    providedIn: 'root'
})
export class StateService extends Observable {
    private state: AppState = {
        user: null,
        isLoggedIn: false,
        theme: 'light'
    };
    
    getUser(): User | null {
        return this.state.user;
    }
    
    setUser(user: User | null): void {
        this.state.user = user;
        this.state.isLoggedIn = !!user;
        this.notifyPropertyChange('user', user);
        this.notifyPropertyChange('isLoggedIn', this.state.isLoggedIn);
    }
    
    isLoggedIn(): boolean {
        return this.state.isLoggedIn;
    }
    
    getTheme(): 'light' | 'dark' {
        return this.state.theme;
    }
    
    setTheme(theme: 'light' | 'dark'): void {
        this.state.theme = theme;
        this.notifyPropertyChange('theme', theme);
    }
}

Angular + RxJS #

使用 BehaviorSubject #

typescript
// services/store.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

interface AppState {
    user: User | null;
    products: Product[];
    cart: CartItem[];
    isLoading: boolean;
}

const initialState: AppState = {
    user: null,
    products: [],
    cart: [],
    isLoading: false
};

@Injectable({
    providedIn: 'root'
})
export class StoreService {
    private state = new BehaviorSubject<AppState>(initialState);
    
    getState(): Observable<AppState> {
        return this.state.asObservable();
    }
    
    getCurrentState(): AppState {
        return this.state.getValue();
    }
    
    setState(partialState: Partial<AppState>): void {
        this.state.next({
            ...this.state.getValue(),
            ...partialState
        });
    }
    
    // 选择器
    selectUser(): Observable<User | null> {
        return new Observable(observer => {
            this.state.subscribe(state => {
                observer.next(state.user);
            });
        });
    }
    
    selectCart(): Observable<CartItem[]> {
        return new Observable(observer => {
            this.state.subscribe(state => {
                observer.next(state.cart);
            });
        });
    }
    
    selectCartTotal(): Observable<number> {
        return new Observable(observer => {
            this.state.subscribe(state => {
                const total = state.cart.reduce(
                    (sum, item) => sum + item.price * item.quantity,
                    0
                );
                observer.next(total);
            });
        });
    }
}

使用服务 #

typescript
// services/user.service.ts
import { Injectable } from '@angular/core';
import { StoreService } from './store.service';
import { ApiService } from './api.service';

@Injectable({
    providedIn: 'root'
})
export class UserService {
    constructor(
        private store: StoreService,
        private api: ApiService
    ) {}
    
    async login(email: string, password: string): Promise<void> {
        this.store.setState({ isLoading: true });
        
        try {
            const user = await this.api.post<User>('/auth/login', { email, password });
            this.store.setState({ user, isLoading: false });
        } catch (error) {
            this.store.setState({ isLoading: false });
            throw error;
        }
    }
    
    logout(): void {
        this.store.setState({ user: null });
    }
}

组件中使用 #

typescript
// components/home/home.component.ts
import { Component, OnInit } from '@angular/core';
import { StoreService } from '../../services/store.service';

@Component({
    selector: 'ns-home',
    templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {
    user$: Observable<User | null>;
    cart$: Observable<CartItem[]>;
    
    constructor(private store: StoreService) {
        this.user$ = this.store.selectUser();
        this.cart$ = this.store.selectCart();
    }
    
    ngOnInit() {}
}

NgRx 状态管理 #

安装 NgRx #

bash
npm install @ngrx/store @ngrx/effects @ngrx/store-devtools

定义状态 #

typescript
// store/user/user.model.ts
export interface User {
    id: number;
    name: string;
    email: string;
}

// store/user/user.state.ts
export interface UserState {
    user: User | null;
    isLoading: boolean;
    error: string | null;
}

export const initialState: UserState = {
    user: null,
    isLoading: false,
    error: null
};

定义 Actions #

typescript
// store/user/user.actions.ts
import { createAction, props } from '@ngrx/store';

export const login = createAction(
    '[User] Login',
    props<{ email: string; password: string }>()
);

export const loginSuccess = createAction(
    '[User] Login Success',
    props<{ user: User }>()
);

export const loginFailure = createAction(
    '[User] Login Failure',
    props<{ error: string }>()
);

export const logout = createAction('[User] Logout');

定义 Reducer #

typescript
// store/user/user.reducer.ts
import { createReducer, on } from '@ngrx/store';
import * as UserActions from './user.actions';

export const userReducer = createReducer(
    initialState,
    
    on(UserActions.login, (state) => ({
        ...state,
        isLoading: true,
        error: null
    })),
    
    on(UserActions.loginSuccess, (state, { user }) => ({
        ...state,
        user,
        isLoading: false,
        error: null
    })),
    
    on(UserActions.loginFailure, (state, { error }) => ({
        ...state,
        isLoading: false,
        error
    })),
    
    on(UserActions.logout, (state) => ({
        ...state,
        user: null,
        error: null
    }))
);

定义 Effects #

typescript
// store/user/user.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import * as UserActions from './user.actions';
import { AuthService } from '../../services/auth.service';

@Injectable()
export class UserEffects {
    login$ = createEffect(() =>
        this.actions$.pipe(
            ofType(UserActions.login),
            mergeMap(({ email, password }) =>
                this.authService.login(email, password).pipe(
                    map(user => UserActions.loginSuccess({ user })),
                    catchError(error => of(UserActions.loginFailure({ error: error.message })))
                )
            )
        )
    );
    
    constructor(
        private actions$: Actions,
        private authService: AuthService
    ) {}
}

定义 Selectors #

typescript
// store/user/user.selectors.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { UserState } from './user.state';

export const selectUserState = createFeatureSelector<UserState>('user');

export const selectUser = createSelector(
    selectUserState,
    (state: UserState) => state.user
);

export const selectIsLoading = createSelector(
    selectUserState,
    (state: UserState) => state.isLoading
);

export const selectError = createSelector(
    selectUserState,
    (state: UserState) => state.error
);

export const selectIsLoggedIn = createSelector(
    selectUser,
    (user: User | null) => !!user
);

配置 Store #

typescript
// app.module.ts
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { userReducer } from './store/user/user.reducer';
import { UserEffects } from './store/user/user.effects';

@NgModule({
    imports: [
        NativeScriptModule,
        StoreModule.forRoot({ user: userReducer }),
        EffectsModule.forRoot([UserEffects]),
        StoreDevtoolsModule.instrument()
    ]
})
export class AppModule {}

组件中使用 #

typescript
// components/login/login.component.ts
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import * as UserActions from '../../store/user/user.actions';
import { selectIsLoading, selectError } from '../../store/user/user.selectors';

@Component({
    selector: 'ns-login',
    templateUrl: './login.component.html'
})
export class LoginComponent {
    email: string = '';
    password: string = '';
    
    isLoading$ = this.store.select(selectIsLoading);
    error$ = this.store.select(selectError);
    
    constructor(private store: Store) {}
    
    onLogin() {
        this.store.dispatch(UserActions.login({
            email: this.email,
            password: this.password
        }));
    }
}

Vuex 状态管理 (Vue) #

安装 Vuex #

bash
npm install vuex

创建 Store #

typescript
// store/index.ts
import Vue from 'nativescript-vue';
import Vuex from 'vuex';

Vue.use(Vuex);

interface State {
    user: User | null;
    products: Product[];
    cart: CartItem[];
    isLoading: boolean;
}

export default new Vuex.Store({
    state: {
        user: null,
        products: [],
        cart: [],
        isLoading: false
    } as State,
    
    getters: {
        isLoggedIn: state => !!state.user,
        cartTotal: state => state.cart.reduce(
            (sum, item) => sum + item.price * item.quantity,
            0
        ),
        cartItemCount: state => state.cart.reduce(
            (sum, item) => sum + item.quantity,
            0
        )
    },
    
    mutations: {
        SET_USER(state, user) {
            state.user = user;
        },
        SET_PRODUCTS(state, products) {
            state.products = products;
        },
        ADD_TO_CART(state, product) {
            const existingItem = state.cart.find(item => item.id === product.id);
            if (existingItem) {
                existingItem.quantity++;
            } else {
                state.cart.push({ ...product, quantity: 1 });
            }
        },
        REMOVE_FROM_CART(state, productId) {
            const index = state.cart.findIndex(item => item.id === productId);
            if (index > -1) {
                state.cart.splice(index, 1);
            }
        },
        SET_LOADING(state, isLoading) {
            state.isLoading = isLoading;
        }
    },
    
    actions: {
        async login({ commit }, { email, password }) {
            commit('SET_LOADING', true);
            try {
                const user = await authService.login(email, password);
                commit('SET_USER', user);
            } finally {
                commit('SET_LOADING', false);
            }
        },
        
        async fetchProducts({ commit }) {
            commit('SET_LOADING', true);
            try {
                const products = await api.getProducts();
                commit('SET_PRODUCTS', products);
            } finally {
                commit('SET_LOADING', false);
            }
        },
        
        addToCart({ commit }, product) {
            commit('ADD_TO_CART', product);
        },
        
        removeFromCart({ commit }, productId) {
            commit('REMOVE_FROM_CART', productId);
        }
    }
});

使用 Store #

vue
<!-- components/Home.vue -->
<template>
    <Page>
        <ActionBar title="Home" />
        <GridLayout>
            <StackLayout>
                <Label :text="user ? user.name : 'Guest'" />
                <Label :text="'Cart: ' + cartItemCount" />
                <Button text="Add to Cart" @tap="addToCart" />
            </StackLayout>
        </GridLayout>
    </Page>
</template>

<script>
import { mapGetters, mapActions } from 'vuex';

export default {
    computed: {
        ...mapGetters(['isLoggedIn', 'cartItemCount']),
        user() {
            return this.$store.state.user;
        }
    },
    
    methods: {
        ...mapActions(['addToCart']),
        
        addToCart() {
            this.$store.dispatch('addToCart', this.product);
        }
    }
};
</script>

最佳实践 #

状态设计原则 #

text
┌─────────────────────────────────────────────────────────────┐
│                    状态设计原则                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 单一数据源                                              │
│     应用状态存储在单一 store 中                             │
│                                                             │
│  2. 状态只读                                                │
│     不能直接修改状态,只能通过 action/mutation              │
│                                                             │
│  3. 纯函数修改                                              │
│     Reducer/Mutation 是纯函数                               │
│                                                             │
│  4. 异步操作分离                                            │
│     异步操作放在 Effects/Actions 中                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

性能优化 #

typescript
// 使用选择器避免重复计算
export const selectCartTotal = createSelector(
    selectCart,
    (cart: CartItem[]) => cart.reduce(
        (sum, item) => sum + item.price * item.quantity,
        0
    )
);

// 组件中使用
this.cartTotal$ = this.store.select(selectCartTotal);

下一步 #

现在你已经掌握了状态管理,接下来学习 动画系统,了解如何添加动画效果!

最后更新:2026-03-29