1.0 入門篇
WordPress-Plugin-Boilerplate-Tutorial 更为简洁的架构方案
React Native Elements开发环境 & 生成项目 & 虚拟机调试 & 本地生成APK档 & 虚拟机运行APK档
2.0 Ignite框架
Ignite是reactnative里最最齊全的軍火庫。
https://github.com/infinitered/ignite
3.0 ReactNative庫
以下是常用的RN庫。一部份已在Ignite框架,但仍需自行安裝另一些常用庫。
已在Ignite框架:
Library | Category | Description |
React Navigation | Navigation | 必需先讀doc。 |
MobX-State-Tree | State Management | 必需先讀doc。 |
MobX-React-Lite | React Integration | / |
apisauce | REST client | 采用Wordpress做後台。所以用哪個api庫不重要。 |
自行安裝:
Library | Category | Description |
react-native-sqlite-storage | Persistence | / |
moment | FormatDatetime | / |
react-native-vector-icons | Icon Framework | 常見icon集合 |
@react-navigation/material-top-tabs react-native-tab-view react-native-pager-view |
UI Framework | React Navigation Toptabs庫 |
@rneui/base @rneui/themed |
UI Framework | / |
react-native-chart-kit react-native-svg |
UI Chart Framework | / |
4.0 Quick Start & Build App
4.1 創建項目
1 | npx ignite-cli@latest new {PROJECT_NAME} --yes |
4.2 更改icon image
{PROJECT_NAME}\ignite\templates\app-icon
1 | npx ignite-cli generate app-icon android |
Build App icon。
4.3 更改啟動splash
{PROJECT_NAME}\ignite\templates\splash-screen
1 | npx ignite-cli generate splash-screen FF0000 |
build splash。
4.4 更改android app名稱
{PROJECT_NAME}\android\app\src\main\res\values\strings.xml
1 2 3 | <resources> <string name= "app_name" >Smoke507</string> </resources> |
app_name就是android app名稱
4.5 Build App
1 2 3 | adb devices -l npm bundle:android cd android && gradlew assembleDebug && adb install %PROJECT_ROOT%\android\app\build\outputs\apk\debug\app-debug.apk |
1行: 檢查devices。2行: compile。3行: Build APK。
5.0 Ignite框架
5.1 Ignite結構
在{PROJECT_NAME}\app下的Ignite結構。
PATH | Description | Files |
components | Ignite自帶的漂亮組件 | |
config | 項目設置 |
config.base.ts,通用 config.dev.ts,開發環境 config.prod.ts,生產環境 |
i18n | 國際化 | |
models | MobX-State-Tree的Store | |
navigators | React Navigation的狀態流 | |
screens | APP視圖 | |
services |
網絡層 數據持久化層 |
|
theme | Ignite自帶的主題 | |
utils | 工具庫 |
5.2 Navigators
1 | npx ignite-cli generate navigator CounterNavigator |
用Generators自動生成。組件在{PROJECT_NAME}\ignite\app\navigators。
{PROJECT_NAME}\ignite\app\navigators\AppNavigator.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import { CounterTopTabsNavigator, CounterTopTabsNavigatorParamList } from "./CounterTopTabsNavigator" export type AppStackParamList = { CounterNavigator: NavigatorScreenParams<CounterTabParamList> } const AppStack = observer( function AppStack() { const { authenticationStore: { isAuthenticated }, } = useStores() return ( <Stack.Navigator initialRouteName= 'CounterNavigator' > <Stack.Screen name= "CounterNavigator" component={CounterNavigator} /> </Stack.Navigator> ) }) |
在AppNavigator主組件,載入剛剛自己的CounterNavigator。
5.3 Screen 視圖
1 | npx ignite-cli generate screen CounterHome |
用Generators自動生成。組件在{PROJECT_NAME}\ignite\app\screens。
{PROJECT_NAME}\ignite\app\navigators\CounterHomeScreen.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import React, { FC } from "react" import { observer } from "mobx-react-lite" import { ViewStyle } from "react-native" import { NativeStackScreenProps } from "@react-navigation/native-stack" import { AppStackScreenProps } from "app/navigators" import { Screen, Text } from "app/components" // import { useNavigation } from "@react-navigation/native" // import { useStores } from "app/models" interface CounterHomeScreenProps extends NativeStackScreenProps<AppStackScreenProps< "CounterHome" >> {} export const CounterHomeScreen: FC<CounterHomeScreenProps> = observer( function CounterHomeScreen() { // Pull in one of our MST stores // const { someStore, anotherStore } = useStores() // Pull in navigation via hook // const navigation = useNavigation() return ( <Screen style={$root} preset= "scroll" > <Text text= "counterHome" /> </Screen> ) }) const $root: ViewStyle = { flex: 1, } |
Screen模版。所有數據都必需由Store映射渣染。
5.3.1 把新視圖 載入 Navigators
{PROJECT_NAME}\ignite\app\navigators\CounterNavigator.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import { CounterHomeScreen } from "../screens" export type CounterTabParamList = { CounterHome: undefined, } const Tab = createBottomTabNavigator<CounterTabParamList>() export const CounterNavigator = () => { const { bottom } = useSafeAreaInsets() return ( <Tab.Navigator screenOptions={{ headerShown: false , tabBarHideOnKeyboard: true , }} > <Tab.Screen name= "CounterHomeScreen" component={CounterHomeScreen} options={{ tabBarLabel: translate( "CounterNavigator.CounterHomeTab" ), tabBarIcon: ({ focused }) => ( <Icon name={focused ? "home" : "home-outline" } type= "ionicon" color={focused && colors.tint} size={30} /> ), unmountOnBlur: false , }} /> </Tab.Navigator> ) } |
在Tab.Screen綁定CounterHomeScreen。注意,只有BottomTabNavigator才有unmountOnBlur強制轉頁刷新。
5.4 MobX-State-Tree
由於經常切換視圖,因此要保持數據的有效性,必需用MST做數據Store中心化。
SMT用了Factory模式。經由SmokeStore去生產和和管理DataObject。
因此,任何DataObject必需包括: Store & Self。
1 | npx ignite-cli generate Counter |
創建Somke。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import { Instance, SnapshotIn, SnapshotOut, types } from "mobx-state-tree" import { withSetPropAction } from "./helpers/withSetPropAction" /** * Model description here for TypeScript hints. */ export const CounterModel = types .model( "Counter" ) .props({}) .actions(withSetPropAction) .views((self) => ({})) // eslint-disable-line @typescript-eslint/no-unused-vars .actions((self) => ({})) // eslint-disable-line @typescript-eslint/no-unused-vars export interface Counter extends Instance< typeof CounterModel> {} export interface CounterSnapshotOut extends SnapshotOut< typeof CounterModel> {} export interface CounterSnapshotIn extends SnapshotIn< typeof CounterModel> {} export const createCounterDefaultModel = () => types.optional(CounterModel, {}) |
Somke模板。
Function | Description |
model | 實体名稱 |
props | 屬性鍵 |
views | 返回屬性 |
actions | 修改屬性 |
5.4.1 加載Smoke
{PROJECT_ROOT}\app\models\index.ts
1 | export * from "./Counter" |
加載Smoke到index.ts
{PROJECT_ROOT}\app\models\RootStore.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import { Instance, SnapshotOut, types } from "mobx-state-tree" import { CounterModel } from "./Counter" /** * A RootStore model. */ export const RootStoreModel = types.model( "RootStore" ).props({ counter: types.optional(CounterModel, {} as any), }) /** * The RootStore instance. */ export interface RootStore extends Instance< typeof RootStoreModel> {} /** * The data of a RootStore. */ export interface RootStoreSnapshot extends SnapshotOut< typeof RootStoreModel> {} |
1. import SmokeModel,2. 加載SmokeModel到RootStoreModel。
5.4.2 創建SmokeStore
{PROJECT_ROOT}\app\models\CounterStore.ts
1 | npx ignite-cli generate CounterStore |
創建SomkeStore。
5.4.3 SmokeStore模版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | import { Instance, SnapshotIn, SnapshotOut, flow, types } from "mobx-state-tree" import { Counter, CounterModel } from "./Counter" import { withSetPropAction } from "./helpers/withSetPropAction" /** * Model description here for TypeScript hints. */ export const CounterStoreModel = types .model( "CounterStore" ) .props({ counters: types.array(CounterModel), }) .actions(withSetPropAction) .views((store) => ({ get items() { return store.counters }, })) // eslint-disable-line @typescript-eslint/no-unused-vars .actions((store) => ({ async fetchCounters() { }, addCounter: flow( function * (counter: Counter) { }), removeCounter: flow( function * (counter: Counter) { }), })) // eslint-disable-line @typescript-eslint/no-unused-vars export interface CounterStore extends Instance< typeof CounterStoreModel> {} export interface CounterStoreSnapshotOut extends SnapshotOut< typeof CounterStoreModel> {} export interface CounterStoreSnapshotIn extends SnapshotIn< typeof CounterStoreModel> {} export const createCounterStoreDefaultModel = () => types.optional(CounterStoreModel, {}) |
SmokeStore模版
1. import之前創建的SmokeModel
2. 創建 <array - CounterModel> counters
3. 所有actions操作,必而flow。
1 2 3 4 5 6 7 8 | addCounter: flow( function * (counter: Counter) { var response = yield api.addCounter(counter) if (response.data.status == 'success' ) { counter.setRowid(response.data.post_id) store.counters.push(counter) } return response }), |
用flow yield 取代 async await。 詳細可參考: https://mobx-state-tree.js.org/concepts/async-actions
5.4.4 加載SmokeStore
{PROJECT_ROOT}\app\models\index.ts
{PROJECT_ROOT}\app\models\RootStore.ts
在index.ts & RootStore.ts 加載 SmokeStore。
6.0 API Service
{PROJECT_ROOT}\app\services\api\api.ts
網絡層。
7.0 Screen 使用 MST Store
{PROJETC_ROOT}\app\screens\CounterHomeScreen.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import { useStores } from "app/models" interface CounterHomeScreenProps extends NativeStackScreenProps<AppStackScreenProps< "CounterHome" >> {} export const CounterHomeScreen: FC<CounterHomeScreenProps> = observer( function CounterHomeScreen(_props) { const { counterStore } = useStores() useEffect(() => { ;(async function load() { counterStore.fetchCounters() })() }, [counterStore]) return ( <Screen preset= "scroll" safeAreaEdges={[ "top" ]}> {{ counterStore.items() }} </Screen> ) }) |
1. useStores() 載入 counterStore
2. useEffect是React生命周期。用await call counterStore.fetchCounters()。
3. react會自動渣染 counterStore.items()。
7.1 Screen Listitem優化
https://reactnative.dev/docs/flatlist
這種List控件,應用優化版的Flatlist。自帶 動態渣染 效果。
8.0 Ignite總彙
1.0 Create ingite project
1.1 Change app_icon, app_name, app_splash
1.2 Build APK
2.0 Create Bottom Navigators
3.0 Create Screen
3.1 Import Screen to Navigators
4.0 MST
4.1 Create Smoke & SmokeStore
4.2 Import SmokeStore to Screen
5.0 Use MST on Screen
Quick start Ignite!
9.0 Wordpress Plugin
https://wppb.me/
創建wordpress plugin。
CleanAPI就是自己創建的Plugin。用來生產優雅的後端API。
Plugin根目錄: {PROJECT_ROOT}\wp-content\plugins\clean-api\
9.1 WP-CPT 後台CMS
https://generatewp.com/post-type/
用generatewp生成CPT源碼。這個是用來生成Wordpress CMS視圖。
{PRODUCT_ROOT}\wp-content\plugins\clean-api\public\classes\junkFoodDataCPT.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | <?php new junkFoodDataCPT(); class junkFoodDataCPT { function __construct() { $this ->post_type = 'junk_food_data' ; $this ->post_name = 'Food Data' ; add_action( 'init' , [ $this , "custom_post_type_{$this->post_type}" ] ); add_filter( "manage_{$this->post_type}_posts_columns" , [ $this , "set_{$this->post_type}_columns" ] ); add_action( "manage_{$this->post_type}_posts_custom_column" , [ $this , "set_{$this->post_type}_custom_column" ], 10, 2 ); add_action( 'admin_init' , [ $this , "set_{$this->post_type}_meta_boxes" ], 2); add_action( 'edit_form_after_title' , [ $this , "set_{$this->post_type}_header" ], 2); } public function custom_post_type_junk_food_data() { $labels = array ( 'name' => _x( $this ->post_name, 'Post Type General Name' , $this ->post_type ), 'singular_name' => _x( $this ->post_name, 'Post Type Singular Name' , $this ->post_type ), 'menu_name' => __( $this ->post_name, $this ->post_type ), 'name_admin_bar' => __( $this ->post_name, $this ->post_type ), 'archives' => __( 'Item Archives' , $this ->post_type ), 'attributes' => __( 'Item Attributes' , $this ->post_type ), 'parent_item_colon' => __( 'Parent Item:' , $this ->post_type ), 'all_items' => __( 'All Items' , $this ->post_type ), 'add_new_item' => __( 'Add New Item' , $this ->post_type ), 'add_new' => __( 'Add New' , $this ->post_type ), 'new_item' => __( 'New Item' , $this ->post_type ), 'edit_item' => __( 'Edit Item' , $this ->post_type ), 'update_item' => __( 'Update Item' , $this ->post_type ), 'view_item' => __( 'View Item' , $this ->post_type ), 'view_items' => __( 'View Items' , $this ->post_type ), 'search_items' => __( 'Search Item' , $this ->post_type ), 'not_found' => __( 'Not found' , $this ->post_type ), 'not_found_in_trash' => __( 'Not found in Trash' , $this ->post_type ), 'featured_image' => __( 'Featured Image' , $this ->post_type ), 'set_featured_image' => __( 'Set featured image' , $this ->post_type ), 'remove_featured_image' => __( 'Remove featured image' , $this ->post_type ), 'use_featured_image' => __( 'Use as featured image' , $this ->post_type ), 'insert_into_item' => __( 'Insert into item' , $this ->post_type ), 'uploaded_to_this_item' => __( 'Uploaded to this item' , $this ->post_type ), 'items_list' => __( 'Items list' , $this ->post_type ), 'items_list_navigation' => __( 'Items list navigation' , $this ->post_type ), 'filter_items_list' => __( 'Filter items list' , $this ->post_type ), ); $args = array ( 'label' => __( $this ->post_name, $this ->post_type ), 'description' => __( $this ->post_name, $this ->post_type ), 'labels' => $labels , 'hierarchical' => false, 'public' => true, 'show_ui' => true, 'show_in_menu' => true, 'menu_icon' => 'dashicons-portfolio' , 'menu_position' => 5, 'show_in_admin_bar' => false, 'show_in_nav_menus' => true, 'can_export' => true, 'has_archive' => true, 'exclude_from_search' => false, 'publicly_queryable' => true, 'capability_type' => 'page' , 'show_in_rest' => true, ); register_post_type( $this ->post_type, $args ); } public function set_junk_food_data_columns( $columns ) { } public function set_junk_food_data_custom_column( $column_id , $post_id ) { } public function set_junk_food_data_meta_boxes() { } public function set_junk_food_data_header() { } } |
CPT必需是Class Object。
{PROJECT_ROOT}\wp-content\plugins\clean-api\public\class-clean-api-public.php
1 2 3 4 5 6 7 | public function __construct( $plugin_name , $version ) { $this ->plugin_name = $plugin_name ; $this ->version = $version ; require_once ( "classes/junkFoodDataCPT.php" ); } |
把剛剛創建的junkFoodDataCPT.php,加載到class-clean-api-public.php。
9.1.1 WP-CPT Edit Page
https://www.advancedcustomfields.com/
像BI軟件(現在流行叫低代瑪)那樣,可自行創建輸入頁面。
數據對象需是wordpress postType。
用戶資料需是user group。
9.2 API Service
{PROHECT_ROOT}\wp-content\plugins\clean-api\public\classes\apiV1.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | add_action( 'rest_api_init' , function () { register_rest_route( $this ->plugin_name. '/v1' , '/get-smoke/all' , array ( 'methods' => 'POST' , 'callback' => 'get_smoke_all' , )); }); add_action( 'rest_api_init' , function () { register_rest_route( $this ->plugin_name. '/v1' , '/save-smoke' , array ( 'methods' => 'POST' , 'callback' => 'save_smoke' , 'args' => array ( 'dt' => array ( 'required' => true), 'user' => array ( 'required' => true), 'area' => array ( 'required' => true), 'noItem' => array ( 'required' => true), ), )); }); function get_smoke_all( $data ) { $posts = get_posts( array ( 'post_status' => 'publish' , 'numberposts' => -1, 'post_type' => 'smoke_data' , 'meta_key' => 'dt' , 'orderby' => 'meta_value' , 'order' => 'DESC' , ) ); $posts2 = array (); foreach ( $posts as $k1 => $v1 ) { $post = array ( 'rowid' => $v1 ->ID); foreach ( array ( 'dt' , 'user' , 'area' , 'noItem' ) as $v2 ) { $post [ $v2 ] = get_post_meta( $v1 ->ID, $v2 , true); if ( $v2 == 'noItem' ) { $post [ $v2 ] = (int) $post [ $v2 ]; } } $posts2 [] = $post ; } if ( $posts2 ) { return wp_send_json( array ( 'status' => 'success' , 'data' => $posts2 ), 200); } else { return wp_send_json( array ( 'status' => 'fail' , 'error' => $posts2 ), 200); } } function save_smoke( $data ) { $post = array ( 'post_status' => 'publish' , 'post_type' => 'smoke_data' , 'meta_input' => array ( 'dt' => $data [ 'dt' ], 'user' => $data [ 'user' ], 'area' => $data [ 'area' ], 'noItem' => $data [ 'noItem' ], ), ); $res = wp_insert_post( $post ); if ( $res ) { return wp_send_json( array ( 'status' => 'success' , 'post_id' => $res ), 200); } else { return wp_send_json( array ( 'status' => 'fail' , 'error' => $res ), 200); } } |
1. register_rest_route可用前置器,設置需required哪些args。
2. 所有dataobject都是用WP Post type來存取。
{PROJECT_ROOT}\wp-content\plugins\clean-api\public\class-clean-api-public.php
1 2 3 4 5 6 7 8 | public function __construct( $plugin_name , $version ) { $this ->plugin_name = $plugin_name ; $this ->version = $version ; require_once ( "classes/apiV1.php" ); require_once ( "classes/junkFoodDataCPT.php" ); } |
把剛剛創建的apiV1.php,加載到class-clean-api-public.php。
9.3 Wordpress User Management
用Wordpress自帶的user group來管理用戶。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具