ReactNative имитирует программу по аренде автомобилей

Android TypeScript React.js APK
ReactNative имитирует программу по аренде автомобилей

О реагированной

Мы представим React-Native на примере, далее именуемый RN. RN — это родное мобильное приложение, которое позволяет вам писать на Javascript. Он совместим с React по принципу дизайна и создает красочный пользовательский интерфейс с помощью механизма декларативных компонентов.

китайский документ

Эта статья разделена на следующие пункты

  • Создание среды RN
  • Инкапсулировать некоторые публичные методы, запросы, локальное хранилище
  • использовать машинописный текст
  • Используйте инструмент управления состоянием redux
  • использовать иконочный шрифт
  • BackHandler
  • эффект страницы
  • Упакованный APK-файл для Android

1. Создайте среду RN

  • Установить

фактическиДокументацияВыше очень четко написано, и очень дружелюбно разделить платформу разработки и целевую платформу, В принципе, вы можете сделать это в соответствии с вышеизложенным. Я использовал свою собственную настоящую машину Xiaomi Mi Note3 для разработки. Пошагово следуйте примеру на официальном сайте. Установка проста

  • бегать

react-native run-android

  • возникшие проблемы
  1. не могу найтиANDROID_HOMEпеременная среды

image

Выполнить один раз в текущем терминалеsource ~/.bash_profile,задача решена.

  • когда он работает успешно

image

  • отладка
  1. react-native log-android

Данные из вашего console.log будут выведены в терминал, что не повлияет на скорость работы программы.

  1. Debug JS Remotely

Встряхните телефон, чтобы открыть меню параметров разработчика, выберите «Отладка JS удаленно», браузер автоматически откроет страницу отладки.http://localhost:8081/debugger-ui, ChromeВы не можете напрямую видеть структуру пользовательского интерфейса приложения, а только предоставляете консольный вывод.

  1. React Developer Tools

Этот плагин может видеть макет плагина интерфейса страницы, а также такие свойства, как props, но, похоже, он не может видеть содержимое console.log.

  1. React Native Debugger Документация

Поскольку я занимаюсь отладкой на реальной машине, мне нужно настроитьsetupDevtools.js

    reactDevTools.connectToDevTools({
      isAppActive,
      host:'你电脑的ip地址',
      // Read the optional global variable for backward compatibility.
      // It was added in https://github.com/facebook/react-native/commit/bf2b435322e89d0aeee8792b1c6e04656c2719a0.
      port: window.__REACT_DEVTOOLS_PORT__,
      resolveRNStyle: require('flattenStyle'),
    });

Затем выполните вторую операцию выше, откройтеDebug JS Remotely. Эта отладка относительно крутая, как в console.log, так и в макете UI, но моя повлияет на работу программы, и будет зависание.

image

  • Отключите кабель передачи данных для разработки

Если вы не хотите оставлять кабель для передачи данных подключенным, вы можете отлаживать свою программу по сети. Когда вы запускаете react-native run-android в первый раз, вам нужно подключить кабель для передачи данных, а затем вы можете ввестиDev Settings -> Debud server host & port for deviceЗаполнить[你开发电脑的ip]:8081, который можно использовать при следующем повторном запускеreact-native startбегать.

image

  • Hot Reload

частичное обновление

  • Live Reload

Перезапустите все приложение один раз


2. Структура каталогов

image

  • API:Соответствующие интерфейсы функциональных модулей размещены в файле, например, функции, связанные с бронированием автомобиля, размещены в aboutBookCar.js.
  • ресурсы:Поместите некоторые статические ресурсы, такие как изображения или шрифты.
  • общий:Ставьте общедоступные методы.
  • компоненты:Разместите общие компоненты, которые делятся на функциональные компоненты и компоненты пользовательского интерфейса.
  • редукс:связанные с редуксом.
  • стили:общественный стиль.
  • типы:файл декларации ts.
  • Просмотры:Разместите каждую главную страницу.

3. Каждый основной модуль

  • http-модуль
// http.js 处理请求,储存token

import { AsyncStorage } from 'react-native'
import { login } from '../api/login'
import axios from 'axios'

async function checkStatus(response) {
  // loading
  // 如果http状态码正常,则直接返回数据
  if (response) {
    if (response.data.status === 1) {
      // 成功
      return response.data
    } else if (response.data.status === 2) {
      await setToken()
      return {
        status: 0,
        msg: '重新登录'
      }
      // 重新登录
    } else if (response.data.status === 3) {
      // 数据格式解析异常
    } else {
      // 异常状态下,把错误信息返回去
      return {
        status: -404,
        msg: '网络异常'
      }
    }
  }
}

// 存放token到storage
async function setToken() {
  const res = await login()
  AsyncStorage.setItem('token', res.token)
  return res.token
}

export const Post = async (url, params = {}) => {
  params = {
    ...params,
    lang: 'cn'
  }

  // 当不是登录接口时,从缓存中获取token,若不存在就调用setToken方法
  if (url !== '登录接口') {
    const storageData = await AsyncStorage.getItem('token')
    params['token'] = storageData ? storageData : null
    if (!params.token) {
      params['token'] = await setToken()
    }
  }
  return axios
    .post(url, params)
    .then(async response => {
      const res = await checkStatus(response)
      return res.data
    })
    .catch(function(error) {
      console.log(error)
    })
}

  • модуль хранения
// storage.js ,使用RN自带的AsyncStorage模块,用来储存token

import {AsyncStorage} from 'react-native'

// 保存数据
export const storeData = async (key, param) => {
  try {
    await AsyncStorage.setItem(key, JSON.stringify(param))
  } catch (error) {
    // Error saving data
  }
}

// 读取数据
export const retrieveData = async key => {
  try {
    const value = await AsyncStorage.getItem(key)
    if (value !== null) {
      return value
    }
  } catch (error) {
    // Error retrieving data
    return null
  }
}

  • навигация

Использование навигацииReact Navigation, из-за необходимости использовать навигацию по ящикам и обычные переходы маршрутизации, здесь нужно использоватьstack navigatorа такжеDrawer navigationкомбинировать.

  1. Создать навигацию
import React, { Component } from 'react'
import { createStackNavigator, createDrawerNavigator } from 'react-navigation'

import BookCar from 'views/BookCar'
import UserInfo from 'views/UserInfo'
import SelectPosition from 'views/SelectPosition'
import ReturnPosition from 'views/ReturnPosition'
import PositionDetail from 'views/PositionDetail'
import BookingCarPage from 'views/BookingCarPage'
// 侧面栏
import Journey from 'views/Journey/index'
import ChargingRule from 'views/ChargingRule/index'
import Recharge from 'views/Recharge/index'
import Wallet from 'views/Wallet/index'
// 抽屉内容的组件
import DrawerScreen from 'components/Ui/CustomDrawer/index'

// 所有页面
const AllPage = createStackNavigator(
  //设置导航要展示的页面
  {
    BookCar: { screen: BookCar },
    UserInfo: { screen: UserInfo },
    SelectPosition: { screen: SelectPosition },
    ReturnPosition: { screen: ReturnPosition },
    PositionDetail: { screen: PositionDetail },
    BookingCarPage: { screen: BookingCarPage },
    Journey: { screen: Journey },
    ChargingRule: { screen: ChargingRule },
    Recharge: { screen: Recharge },
    Wallet: { screen: Wallet }
  },
  //设置navigationOptions属性对象
  {
    mode: 'card', //设置mode属性,
    headerMode: 'none' // 去掉头部
  }
)
// 结合抽屉跟所有页面
const DrawerNavigator = createDrawerNavigator(
  {
    Home: {
      screen: AllPage // 所有页面
    }
  },
  {
    contentComponent: DrawerScreen, // 用来呈现抽屉内容的组件
    drawerWidth: 250
  }
)

export default class Navigator extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    return <DrawerNavigator />
  }
}
  1. Навигация по приложениям
// App.tsx

import * as React from 'react'

import Navigator from './src/components/Function/Navigator'

export default class App extends React.Component {
  render() {
    return (
        <Navigator />
    )
  }
}

4. typescript

В проекте, который я делал раньше, использовался машинописный текст, и это было очень удобно, поэтому я подключил машинописный текст.

  1. install npm install react-native-typescript-transformer typescript -D

  2. настроить tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "module": "esnext",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "rootDirs": ["./src"],
    "baseUrl": "./src",
    "jsx": "preserve",
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "importHelpers": true,
    "experimentalDecorators": true,
    "lib": ["es7", "dom"],
    "skipLibCheck": true,
    "typeRoots": ["node", "node_modules/@types"],
    "outDir": "./lib"
  },
  "exclude": ["node_modules"],
  "include": ["src/**/*"]
}

  1. Настройте собственный упаковщик реакции Создайте файл в корневом каталоге проектаrn-cli.config.js
module.exports = {
  getTransformModulePath() {
    return require.resolve('react-native-typescript-transformer');
  },
  getSourceExts() {
    return ['ts', 'tsx'];
  }
}
  1. Добавьте файл объявления реакции
npm install @types/react @types/react-native @types/react-navigation -D
  1. Чтобы иметь возможность использовать абсолютные пути, вы можете создать файл package.json в каталоге, на который нужно ссылаться.

Например: создайте файл package.json в каталоге API.

{
  "name": "api"
}

просто используйтеapi/xxxкак путь

  1. запустить проверку
tsc

5. redux

  1. install
npm install react-redux redux redux-actions redux-thunk -S
  1. Измените файл App.tsx
import * as React from 'react'
import { createStore, applyMiddleware, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'

import Navigator from './src/components/Function/Navigator'
import * as reducers from './src/redux/reducers'

const createStoreWithMiddleware = applyMiddleware(thunk)(createStore)
const reducer = combineReducers(reducers)
const store = createStoreWithMiddleware(reducer)

export default class App extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <Navigator />
      </Provider>
    )
  }
}
  1. actions

Ниже приведен пример хранения списка транспортных средств в магазине, существуетreduxСоздано в каталогеactionsпапка,actionsсодержитactionTypes.tsа такжеCarAction.ts

actionTypes.ts

// 声明一下action类型
export const SET_CARLIST = 'SET_CARLIST'

CarAction.ts

import * as types from './actionTypes' // action类型

// action方法
export function setCarList(carList) {
  return {
    type: types.SET_CARLIST,
    carList
  }
}
  1. reducers

существуетreduxСоздано в каталогеreducersпапка,reducersсодержитindex.tsа такжеCarReducer.ts

index.ts

import CarReducer from './CarReducer'

export { CarReducer }

CarReducer.ts

import * as types from '../actions/actionTypes'

const initialState = {
  carList: [],
}

export default function counter(state = initialState, action) {
  switch (action.type) {
    case types.SET_CARLIST:
      return {
        ...state,
        carList: action.carList
      }
    default:
      return state
  }
}
  1. Использование. Теперь, когда базовая операция создана, теперь нужно получить хранилище и выполнить действие.
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as CarAction from '../../redux/actions/CarAction'
import * as UserInfo from '../../redux/actions/UserInfo'
...

// 由于使用的ts,我们可以先定义好interface
interface IStoreProps {
  // 方法
  actions?: {
    setCarList: (v: CarStore.ICarItem[]) => void
  }
  // 数据
  state?: {
    carList: CarStore.ICarItem[]
  }
}

class BookCar extends React.Component<IStoreProps> {
    ...
    
    // 可以通过 this.props.actions.setCarList()来执行action方法
    // 同样,可以通过 this.props.state.carList来获取数据
}

const StateToPoprs = state => ({
  state: { ...state.CarReducer }
})

const dispatchToProps = dispatch => ({
  actions: bindActionCreators({ ...CarAction }, dispatch)
})

export default connect(
  StateToPoprs,
  dispatchToProps
)(BookCar)

6. Использование иконок

  1. install
npm install react-native-vector-icons --save
  1. настроитьandroid/app/build.gradle
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
project.ext.vectoricons = [
   iconFontNames: [ 'iconfont.ttf'] // Name of the font files you want to copy
]
  1. Получите файл .ttf

Доступна сiconfontЧтобы получить его выше, вы можете создать на нем новый проект, затем поместить нужный значок в проект и нажать, чтобы загрузить его локально.

image

image

  1. обработка файлов

После загрузкиiconfont.ttfа такжеiconfont.cssфайл вsrc/assets/fonts/каталог иiconfont.ttfфайл вandroid/app/src/main/assets/fonts/目录下

// iconfont.css
...
.icon-jifei:before { content: "\e602"; }

.icon-quan:before { content: "\e603"; }
...

Нам нужно получить файл json со следующим содержимым

{
  "icon-jifei": 58882,
  "icon-quan": 58883,
  ...
}

На самом деле, это будетiconfont.cssИзвлекается имя стиля внутри и десятичное значение содержимого. Ручное преобразование более хлопотно. Мы добавляем скрипт автоматического преобразования. Создайте новый проект в корневом каталоге проектаtools/getIconfontJson/getIconfontJson.js

const path = require('path')

const oldPath = path.join('./src/assets/fonts/iconfont.css')
const newPath = path.join('./src/assets/fonts/iconfont.json')

var gen = (module.exports = function() {
  const readline = require('readline')
  const fs = require('fs')

  const fRead = fs.createReadStream(oldPath)
  const fWrite = fs.createWriteStream(newPath, {
    flags: 'w+',
    defaultEncoding: 'utf8'
  })

  const objReadLine = readline.createInterface({
    input: fRead
  })

  var ret = {}

  objReadLine.on('line', line => {
    line = line && line.trim()
    if (!line.includes(':before') || !line.includes('content')) return
    var keyMatch = line.match(/\.(.*?):/)
    var valueMatch = line.match(/content:.*?\\(.*?);/)
    var key = keyMatch && keyMatch[1]
    var value = valueMatch && valueMatch[1]
    value = parseInt(value, 16)
    key && value && (ret[key] = value)
  })

  objReadLine.on('close', () => {
    console.log('readline close')
    fWrite.write(JSON.stringify(ret), 'utf8')
  })
})

gen()

После запуска скрипта будетassets/fonts/Файл iconfont.json создается в каталоге.

Наконец, мыcomponentsСоздайте общедоступный компонент в каталогеIconFont

// IconFont/index.tsx
import { createIconSet } from 'react-native-vector-icons'
const glyphMap = require('assets/fonts/iconfont.json')
const IconFont = createIconSet(glyphMap, 'iconfont', 'iconfont.ttf')

export default IconFont
  1. usage
import Icon from 'components/Ui/IconFont/index'
... 

export default class CarList extends React.Component {
    ...
    render() {
        <Icon
            name="icon-jifei"
            size={50}
            color="red"
        />
    }
}

7. BackHandler

  1. Слушайте обратное событие на устройстве. Когда я выбираю точку транспортного средства, появляется нижнее поле. В это время, когда я нажимаю кнопку «Назад», нижнее поле должно исчезнуть.
  2. При переходе на другую страницу предыдущее событие прослушивателя должно быть уничтожено.

image

...

  handleBackPress = () => {}

  // 监听事件
  backHandlerListen = () => {
    BackHandler.addEventListener('hardwareBackPress', this.handleBackPress)
  }
  // 销毁监听
  destroyBackHandlerListen = () => {
    BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress)
  }

  // 重新进入这个页面触发
  didFocus = () => {
    this.props.navigation.addListener('didFocus', payload => {
      this.backHandlerListen()
    })
  }

 // 这个页面失去焦点触发 
  didBlur = () => {
    this.props.navigation.addListener('didBlur', payload => {
      this.destroyBackHandlerListen()
    })
  }

  componentDidMount() {
    this.backHandlerListen()
    this.didFocus()
    this.didBlur()
  }
...

8. Эффекты страницы

Страница пока относительно сырая, а эффект демо-природы реализует основную функцию проката автомобилей.

image

9. Упакованный APK для AndroidДокументация

  1. использоватьkeytoolсгенерировать ключ
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
  1. в каталоге проектаandroid/gradle.propertiesплюс следующая конфигурация
// *****是刚才生成秘钥时候填写的密码
MYAPP_RELEASE_STORE_FILE=my-release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD=*****
MYAPP_RELEASE_KEY_PASSWORD=*****
  1. Настроить build.gradle
...
android {
    ...
    defaultConfig { ... }
    signingConfigs {
        release {
            if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
                storeFile file(MYAPP_RELEASE_STORE_FILE)
                storePassword MYAPP_RELEASE_STORE_PASSWORD
                keyAlias MYAPP_RELEASE_KEY_ALIAS
                keyPassword MYAPP_RELEASE_KEY_PASSWORD
            }
        }
    }
    buildTypes {
        release {
            ...
            signingConfig signingConfigs.release
        }
    }
}
...
  1. Создать пакет APK
$ cd android
$ ./gradlew assembleRelease

Сгенерированный apk-файл находится по адресу生成的 APK 文件位于android/app/build/outputs/apk/app-release.apk, Вы можете напрямую поместить apk на мобильный телефон для установки 5. Обнаружить ошибку

// error
Couldn't follow symbolic link' when testing release build for Android

// fix
rm -rf node_modules && npm install

Спасибо за просмотр

image