О реагированной
Мы представим React-Native на примере, далее именуемый RN. RN — это родное мобильное приложение, которое позволяет вам писать на Javascript. Он совместим с React по принципу дизайна и создает красочный пользовательский интерфейс с помощью механизма декларативных компонентов.
Эта статья разделена на следующие пункты
- Создание среды RN
- Инкапсулировать некоторые публичные методы, запросы, локальное хранилище
- использовать машинописный текст
- Используйте инструмент управления состоянием redux
- использовать иконочный шрифт
- BackHandler
- эффект страницы
- Упакованный APK-файл для Android
1. Создайте среду RN
- Установить
фактическиДокументацияВыше очень четко написано, и очень дружелюбно разделить платформу разработки и целевую платформу, В принципе, вы можете сделать это в соответствии с вышеизложенным. Я использовал свою собственную настоящую машину Xiaomi Mi Note3 для разработки. Пошагово следуйте примеру на официальном сайте. Установка проста
- бегать
react-native run-android
- возникшие проблемы
- не могу найти
ANDROID_HOME
переменная среды
Выполнить один раз в текущем терминалеsource ~/.bash_profile
,задача решена.
- когда он работает успешно
- отладка
- react-native log-android
Данные из вашего console.log будут выведены в терминал, что не повлияет на скорость работы программы.
- Debug JS Remotely
Встряхните телефон, чтобы открыть меню параметров разработчика, выберите «Отладка JS удаленно», браузер автоматически откроет страницу отладки.http://localhost:8081/debugger-ui, ChromeВы не можете напрямую видеть структуру пользовательского интерфейса приложения, а только предоставляете консольный вывод.
- React Developer Tools
Этот плагин может видеть макет плагина интерфейса страницы, а также такие свойства, как props, но, похоже, он не может видеть содержимое console.log.
- 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, но моя повлияет на работу программы, и будет зависание.
- Отключите кабель передачи данных для разработки
Если вы не хотите оставлять кабель для передачи данных подключенным, вы можете отлаживать свою программу по сети. Когда вы запускаете react-native run-android в первый раз, вам нужно подключить кабель для передачи данных, а затем вы можете ввестиDev Settings
-> Debud server host & port for device
Заполнить[你开发电脑的ip]:8081
, который можно использовать при следующем повторном запускеreact-native start
бегать.
- Hot Reload
частичное обновление
- Live Reload
Перезапустите все приложение один раз
2. Структура каталогов
- 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
комбинировать.
- Создать навигацию
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 />
}
}
- Навигация по приложениям
// 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
В проекте, который я делал раньше, использовался машинописный текст, и это было очень удобно, поэтому я подключил машинописный текст.
-
install npm install react-native-typescript-transformer typescript -D
-
настроить 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/**/*"]
}
- Настройте собственный упаковщик реакции
Создайте файл в корневом каталоге проекта
rn-cli.config.js
module.exports = {
getTransformModulePath() {
return require.resolve('react-native-typescript-transformer');
},
getSourceExts() {
return ['ts', 'tsx'];
}
}
- Добавьте файл объявления реакции
npm install @types/react @types/react-native @types/react-navigation -D
- Чтобы иметь возможность использовать абсолютные пути, вы можете создать файл package.json в каталоге, на который нужно ссылаться.
Например: создайте файл package.json в каталоге API.
{
"name": "api"
}
просто используйтеapi/xxx
как путь
- запустить проверку
tsc
5. redux
- install
npm install react-redux redux redux-actions redux-thunk -S
- Измените файл 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>
)
}
}
- 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
}
}
- 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
}
}
- Использование. Теперь, когда базовая операция создана, теперь нужно получить хранилище и выполнить действие.
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. Использование иконок
- install
npm install react-native-vector-icons --save
- настроить
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
]
- Получите файл .ttf
Доступна сiconfontЧтобы получить его выше, вы можете создать на нем новый проект, затем поместить нужный значок в проект и нажать, чтобы загрузить его локально.
- обработка файлов
После загрузки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
- 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
- Слушайте обратное событие на устройстве. Когда я выбираю точку транспортного средства, появляется нижнее поле. В это время, когда я нажимаю кнопку «Назад», нижнее поле должно исчезнуть.
- При переходе на другую страницу предыдущее событие прослушивателя должно быть уничтожено.
...
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. Эффекты страницы
Страница пока относительно сырая, а эффект демо-природы реализует основную функцию проката автомобилей.
9. Упакованный APK для AndroidДокументация
- использовать
keytool
сгенерировать ключ
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
- в каталоге проекта
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=*****
- Настроить 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
}
}
}
...
- Создать пакет 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
Спасибо за просмотр