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 創建項目
npx ignite-cli@latest new {PROJECT_NAME} --yes
4.2 更改icon image
{PROJECT_NAME}\ignite\templates\app-icon
npx ignite-cli generate app-icon android
Build App icon。
4.3 更改啟動splash
{PROJECT_NAME}\ignite\templates\splash-screen
npx ignite-cli generate splash-screen FF0000
build splash。
4.4 更改android app名稱
{PROJECT_NAME}\android\app\src\main\res\values\strings.xml
<resources> <string name="app_name">Smoke507</string> </resources>
app_name就是android app名稱
4.5 Build App
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
npx ignite-cli generate navigator CounterNavigator
用Generators自動生成。組件在{PROJECT_NAME}\ignite\app\navigators。
{PROJECT_NAME}\ignite\app\navigators\AppNavigator.tsx
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 視圖
npx ignite-cli generate screen CounterHome
用Generators自動生成。組件在{PROJECT_NAME}\ignite\app\screens。
{PROJECT_NAME}\ignite\app\navigators\CounterHomeScreen.tsx
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
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。
npx ignite-cli generate Counter
創建Somke。
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
export * from "./Counter"
加載Smoke到index.ts
{PROJECT_ROOT}\app\models\RootStore.ts
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
npx ignite-cli generate CounterStore
創建SomkeStore。
5.4.3 SmokeStore模版
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。
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
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()。
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
<?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
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.2 API Service
{PROHECT_ROOT}\wp-content\plugins\clean-api\public\classes\apiV1.php
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
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來管理用戶。
标签:ignite,__,name,App,詳細,PROJECT,post,type,app From: https://www.cnblogs.com/chenkuang/p/17585313.html