NativeScript React集成 #

React 集成概述 #

NativeScript 与 React 结合,让你可以使用 React 的组件化思想开发原生应用。

text
┌─────────────────────────────────────────────────────────────┐
│                    React NativeScript                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  核心特性                                                    │
│  ├── React 18 支持                                          │
│  ├── Hooks API                                              │
│  ├── JSX 语法                                               │
│  ├── 函数式组件                                             │
│  └── Redux 状态管理                                         │
│                                                             │
│  NativeScript 扩展                                          │
│  ├── 原生 UI 组件                                           │
│  ├── 原生路由                                               │
│  └── 原生模块                                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

项目创建 #

创建 React 项目 #

bash
ns create my-app --react

项目结构 #

text
my-app/
├── app/
│   ├── App.tsx
│   ├── components/
│   │   ├── Home.tsx
│   │   └── Detail.tsx
│   ├── hooks/
│   │   └── useUser.ts
│   └── store/
│       └── index.ts
├── App_Resources/
├── package.json
└── tsconfig.json

应用入口 #

App.tsx #

tsx
// App.tsx
import * as React from 'react';
import { Frame } from '@nativescript/core';
import { Home } from './components/Home';

export function App() {
    return (
        <Frame>
            <Home />
        </Frame>
    );
}

注册应用 #

typescript
// main.ts
import { Application } from '@nativescript/core';
import { App } from './App';

Application.run({
    create: () => {
        return App();
    }
});

组件开发 #

函数式组件 #

tsx
// components/Home.tsx
import * as React from 'react';
import { Page, ActionBar, ActionItem, ListView, Label, Button, GridLayout, StackLayout } from '@nativescript/core';

interface Item {
    id: number;
    title: string;
    price: string;
}

export function Home() {
    const [items, setItems] = React.useState<Item[]>([
        { id: 1, title: 'Item 1', price: '$10' },
        { id: 2, title: 'Item 2', price: '$20' },
        { id: 3, title: 'Item 3', price: '$30' }
    ]);
    
    const addItem = () => {
        setItems([
            ...items,
            {
                id: items.length + 1,
                title: `Item ${items.length + 1}`,
                price: '$0'
            }
        ]);
    };
    
    const onItemTap = (item: Item) => {
        console.log('Tapped:', item);
    };
    
    const renderListItem = (item: Item) => (
        <GridLayout columns="*, auto" onTap={() => onItemTap(item)} key={item.id}>
            <Label text={item.title} col={0} />
            <Label text={item.price} col={1} />
        </GridLayout>
    );
    
    return (
        <Page>
            <ActionBar title="Home">
                <ActionItem text="Add" onTap={addItem} />
            </ActionBar>
            
            <GridLayout>
                <ListView items={items} cellFactory={renderListItem} />
            </GridLayout>
        </Page>
    );
}

使用 Hooks #

tsx
// components/UserProfile.tsx
import * as React from 'react';
import { Page, ActionBar, GridLayout, StackLayout, Image, Label, Button, TextField } from '@nativescript/core';

interface User {
    name: string;
    email: string;
    avatar: string;
}

export function UserProfile({ userId }: { userId: number }) {
    const [user, setUser] = React.useState<User>({
        name: 'John Doe',
        email: 'john@example.com',
        avatar: '~/assets/avatar.png'
    });
    
    const [editMode, setEditMode] = React.useState(false);
    const [editName, setEditName] = React.useState(user.name);
    const [editEmail, setEditEmail] = React.useState(user.email);
    
    const saveProfile = () => {
        setUser({
            ...user,
            name: editName,
            email: editEmail
        });
        setEditMode(false);
    };
    
    return (
        <Page>
            <ActionBar title="Profile" />
            <GridLayout>
                <StackLayout>
                    <Image src={user.avatar} width={100} height={100} borderRadius={50} />
                    <Label text={user.name} className="h2" />
                    <Label text={user.email} className="text-muted" />
                    
                    {!editMode ? (
                        <Button text="Edit" onTap={() => setEditMode(true)} />
                    ) : (
                        <StackLayout>
                            <TextField text={editName} onTextChange={(e) => setEditName(e.value)} hint="Name" />
                            <TextField text={editEmail} onTextChange={(e) => setEditEmail(e.value)} hint="Email" keyboardType="email" />
                            <Button text="Save" onTap={saveProfile} />
                            <Button text="Cancel" onTap={() => setEditMode(false)} />
                        </StackLayout>
                    )}
                </StackLayout>
            </GridLayout>
        </Page>
    );
}

自定义 Hooks #

tsx
// hooks/useUser.ts
import * as React from 'react';
import { UserService } from '../services/user.service';

interface User {
    id: number;
    name: string;
    email: string;
}

interface UseUserResult {
    user: User | null;
    loading: boolean;
    error: Error | null;
    refetch: () => Promise<void>;
}

export function useUser(userId: number): UseUserResult {
    const [user, setUser] = React.useState<User | null>(null);
    const [loading, setLoading] = React.useState(true);
    const [error, setError] = React.useState<Error | null>(null);
    
    const userService = React.useMemo(() => new UserService(), []);
    
    const fetchUser = React.useCallback(async () => {
        setLoading(true);
        setError(null);
        
        try {
            const data = await userService.getUser(userId);
            setUser(data);
        } catch (err) {
            setError(err as Error);
        } finally {
            setLoading(false);
        }
    }, [userId, userService]);
    
    React.useEffect(() => {
        fetchUser();
    }, [fetchUser]);
    
    return {
        user,
        loading,
        error,
        refetch: fetchUser
    };
}
tsx
// 使用自定义 Hook
import * as React from 'react';
import { useUser } from '../hooks/useUser';

export function UserDetail({ userId }: { userId: number }) {
    const { user, loading, error, refetch } = useUser(userId);
    
    if (loading) {
        return <ActivityIndicator busy={true} />;
    }
    
    if (error) {
        return (
            <StackLayout>
                <Label text={`Error: ${error.message}`} />
                <Button text="Retry" onTap={refetch} />
            </StackLayout>
        );
    }
    
    return (
        <StackLayout>
            <Label text={user?.name} />
            <Label text={user?.email} />
        </StackLayout>
    );
}

导航 #

页面导航 #

tsx
// components/Home.tsx
import * as React from 'react';
import { Frame, Page, ActionBar, Button, GridLayout } from '@nativescript/core';
import { Detail } from './Detail';

export function Home() {
    const goToDetail = (id: number) => {
        Frame.topmost().navigate({
            create: () => <Detail id={id} />,
            transition: { name: 'slide' }
        });
    };
    
    return (
        <Page>
            <ActionBar title="Home" />
            <GridLayout>
                <Button text="Go to Detail" onTap={() => goToDetail(1)} />
            </GridLayout>
        </Page>
    );
}

// components/Detail.tsx
import * as React from 'react';
import { Frame, Page, ActionBar, NavigationButton, GridLayout, Label } from '@nativescript/core';

interface DetailProps {
    id: number;
}

export function Detail({ id }: DetailProps) {
    const goBack = () => {
        Frame.topmost().goBack();
    };
    
    return (
        <Page>
            <ActionBar title="Detail">
                <NavigationButton text="Back" onTap={goBack} />
            </ActionBar>
            <GridLayout>
                <Label text={`Item ID: ${id}`} />
            </GridLayout>
        </Page>
    );
}

状态管理 #

使用 Context #

tsx
// context/AppContext.tsx
import * as React from 'react';

interface User {
    id: number;
    name: string;
    email: string;
}

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

interface AppContextType extends AppState {
    setUser: (user: User | null) => void;
    setTheme: (theme: 'light' | 'dark') => void;
}

const AppContext = React.createContext<AppContextType | undefined>(undefined);

export function AppProvider({ children }: { children: React.ReactNode }) {
    const [user, setUser] = React.useState<User | null>(null);
    const [theme, setTheme] = React.useState<'light' | 'dark'>('light');
    
    const value = {
        user,
        theme,
        setUser,
        setTheme
    };
    
    return (
        <AppContext.Provider value={value}>
            {children}
        </AppContext.Provider>
    );
}

export function useAppContext() {
    const context = React.useContext(AppContext);
    if (!context) {
        throw new Error('useAppContext must be used within AppProvider');
    }
    return context;
}
tsx
// 使用 Context
import * as React from 'react';
import { useAppContext } from '../context/AppContext';

export function UserProfile() {
    const { user, setUser } = useAppContext();
    
    return (
        <StackLayout>
            {user ? (
                <>
                    <Label text={user.name} />
                    <Label text={user.email} />
                    <Button text="Logout" onTap={() => setUser(null)} />
                </>
            ) : (
                <Button text="Login" onTap={() => {/* login logic */}} />
            )}
        </StackLayout>
    );
}

使用 Redux #

tsx
// store/index.ts
import { createStore } from 'redux';

interface User {
    id: number;
    name: string;
    email: string;
}

interface AppState {
    user: User | null;
    items: any[];
    isLoading: boolean;
}

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

function reducer(state = initialState, action: any) {
    switch (action.type) {
        case 'SET_USER':
            return { ...state, user: action.payload };
        case 'SET_ITEMS':
            return { ...state, items: action.payload };
        case 'SET_LOADING':
            return { ...state, isLoading: action.payload };
        default:
            return state;
    }
}

export const store = createStore(reducer);

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

export function useUser() {
    return useSelector((state: AppState) => state.user);
}

export function useItems() {
    return useSelector((state: AppState) => state.items);
}

export function useActions() {
    const dispatch = useDispatch();
    
    return {
        setUser: (user: User | null) => dispatch({ type: 'SET_USER', payload: user }),
        setItems: (items: any[]) => dispatch({ type: 'SET_ITEMS', payload: items }),
        setLoading: (isLoading: boolean) => dispatch({ type: 'SET_LOADING', payload: isLoading })
    };
}

组件通信 #

Props 传递 #

tsx
// 父组件
export function Parent() {
    const [data, setData] = React.useState('Hello');
    
    const handleUpdate = (newValue: string) => {
        setData(newValue);
    };
    
    return (
        <GridLayout>
            <Child data={data} onUpdate={handleUpdate} />
        </GridLayout>
    );
}

// 子组件
interface ChildProps {
    data: string;
    onUpdate: (value: string) => void;
}

export function Child({ data, onUpdate }: ChildProps) {
    return (
        <Label text={data} onTap={() => onUpdate('New Value')} />
    );
}

最佳实践 #

组件设计原则 #

text
┌─────────────────────────────────────────────────────────────┐
│                    组件设计原则                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 函数式组件                                              │
│     使用函数式组件和 Hooks                                  │
│                                                             │
│  2. 单一职责                                                │
│     每个组件只做一件事                                      │
│                                                             │
│  3. Props 类型定义                                          │
│     使用 TypeScript 定义 Props 类型                         │
│                                                             │
│  4. 合理拆分                                                │
│     大组件拆分为小组件                                      │
│                                                             │
│  5. 性能优化                                                │
│     使用 useMemo 和 useCallback                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

下一步 #

现在你已经掌握了 React 集成,接下来学习 部署发布,了解如何发布你的应用!

最后更新:2026-03-29