科技美学

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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()。

 

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

<?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.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

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來管理用戶。

 

posted on 2023-08-10 17:10  chankuang  阅读(96)  评论(0编辑  收藏  举报