React-Native基本原理
1. 整体架构
RN框架让js开发者可以大部分使用js代码构建跨平台APP,Facebook官方说法是learn once, run everywhere, 即在Android 、 IOS、 Browser各个平台,程序画UI和写逻辑的方式都大致相同。因为JS 可以动态加载,从而理论上可以做到write once, run everywhere, 当然要做额外的适配处理。如图:
RN需要一个js的运行环境,在ios上直接使用内置的javascriptcore,在android上使用webkit.org官方开源的jsc.so。此外还集成了其他开源组件,如fresco图片组件,okhttp网络组件等。
RN会把应用的js代码(包括依赖的framework)编译成一个js文件(一般命名为index.android.bundle)。RN的整体框架目标就是为了解释运行这个js脚本文件,如果是js扩展的api,则直接通过bridge调用native方法;如果是ui界面,则映射到virtual Dom这个虚拟的js数据结构中,通过bridge传递到native,然后根据数据属性设置各个对应的真实native的view。bridge是一种js和java代码通信的机制,用bridge函数传入对方module和method即可达到异步回调的结果。
对于js开发者来说,画ui只画到virtual DOM中,不需要特别关心具体的平台,还是原来的单线程开发,还是原来的html组装ui(jsx),还是原来的样式模型(部分兼容)。RN的界面处理除了实现View增删改查的接口之外,还自定义一套样式表达CSSLayout,这套CSSLayout也是跨平台实现。RN拥有画ui的跨平台能力,主要是加入了virtual DOM编程模型,该方法一面可以照顾js开发者在htmlDOM的部分传承,另一方面可以让virtualDOM适配实现到各个平台,实现跨平台的能力,并且为未来增加更多的想象空间,比如react-cavas,react-openGL。而实际上react-native也是从reactjs演变而来
2. 组件代码
import React, { Component } from "react";
import { View, Text, StyleSheet, ActivityIndicator } from "react-native";
export default class Main extends Component {
constructor(props) {
super(props);
}
static defaultProps = {
title: "处理中..."
};
render() {
let { title } = this.props;
return (
{title}
);
}
componentDidMount = async () => {};
}
const style = StyleSheet.create({});
对于js开发者来说,整个RN APP就只有一个js文件,而开发者需要编写的就只有如上部分。主要是四个部分:
import所有依赖的组件
class Main extends Component创建app,并且在reader函数中返回ui界面结构,实际经过编译,都会变成js代码。
const style = StyleSheet.create({}) 创建css样式,实际上会直接当作参数反馈到上面的app
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject); 以上三个更像是参数,这个才是JS 程序的入口。即把当前APP的对象注册到AppRegistry组件中, AppRegistry组件是js module
接着就等待Native事件驱动渲染JS端定义的APP组件
四小强之路由的设计封装
1、简单的路由页面配置
import Home from "home/Home";
import NewList from "home/NewList";
import Product from "home/Product";
import NewDetail from "home/NewDetail";
import Icon from "react-native-vector-icons/FontAwesome";
const HomeRoute = {
home: {
screen: Home,
navigationOptions: {
header: null
}
}
};
export default HomeRoute;
2、利用react-navigation对配置文件进行封装
- tabbar的封装
/** @format */
import React, { Component } from "react";
import {
createBottomTabNavigator,
createStackNavigator,
createAppContainer,
getActiveChildNavigationOptions
} from "react-navigation";
import { Icon, Touchable, Button } from "ui";
import { StyleSheet, Platform, View } from "react-native";
import { transformSize } from "@/utils";
import lang from "@/assets/lang";
import home from "./home";
import service from "./service";
import my from "./my";
import common from "./public";
import login from "./login";
let tabNavRouteConfig = {
home: {
screen: home.home.screen,
navigationOptions: ({ screenProps }) => ({
tabBarLabel: screenProps.tabLabel.home,
tabBarIcon: ({ tintColor, focused }) => (
),
header: null
})
},
service: {
screen: service.service.screen,
navigationOptions: ({ screenProps }) => ({
tabBarLabel: screenProps.tabLabel.service,
tabBarIcon: ({ tintColor, focused }) => (
)
})
},
my: {
screen: my.my.screen,
navigationOptions: ({ screenProps }) => ({
tabBarLabel: screenProps.tabLabel.my,
tabBarIcon: ({ tintColor, focused }) => (
)
})
}
};
const TabNav = createBottomTabNavigator(tabNavRouteConfig, {
animationEnabled: false,
swipeEnabled: false,
lazy: true,
tabBarPosition: "bottom",
tabBarOptions: {
showIcon: true,
activeTintColor: "#3fc375",
inactiveTintColor: "#494949",
labelStyle: {
fontSize: 12
},
style: {
height: transformSize(110)
}
}
});
TabNav.navigationOptions = ({ navigation, screenProps }) => {
return getActiveChildNavigationOptions(navigation, screenProps);
};
- 整体路由的封装
// const AppContainer = createAppContainer(TabNav);
const AppNavigator = createStackNavigator(
{
App: TabNav,
...home,
...service,
...my,
...common,
...login
},
{
defaultNavigationOptions: ({ navigation }) => StackOptions({ navigation }),
cardShadowEnabled: false,
cardOverlayEnabled: true
}
);
const AppContainer = createAppContainer(AppNavigator);
export default AppContainer;
const StackOptions = ({ navigation }) => {
const tabBarVisible = false;
const headerBackTitle = false;
const headerStyle = {
backgroundColor: "#fff",
borderBottomWidth: 1,
borderBottomColor: "#edebed",
elevation: 0
};
const headerTitleStyle = {
flex: 1,
textAlign: "center",
fontWeight: "600",
color: "#333",
fontSize: transformSize(36)
};
const headerTintColor = "#3fc375";
const headerLeft = (
四小强之axios的封装结合
1、封装页面和axios调用的类
/** @format */
import axios from "./axios";
import Qs from "qs";
import _pick from "lodash/pick";
import _assign from "lodash/assign";
import _merge from "lodash/merge";
import _isEmpty from "lodash/isEmpty";
import _isArray from "lodash/isArray";
import { assert } from "@/utils";
import { API_DEFAULT_CONFIG, AXIOS_DEFAULT_CONFIG } from "@/config";
import API_CONFIG from "@/api";
/**
* 生成api接口类
*/
class Api {
constructor(options) {
this.api = {};
this.apiBuilder(options);
}
/**
* 创建工程接口
* @param sep 分隔符
* @param config 接口配置对象
* @param mock 是否开启mock
* @param debug 是否开启debug模式
* @param mockBaseURL mock接口地址
*/
apiBuilder({
sep = "/",
config = {},
mock = false,
debug = false,
mockBaseURL = ""
}) {
Object.keys(config).map(namespace => {
this._apiSingleBuilder({
namespace,
mock,
mockBaseURL,
sep,
debug,
config: config[namespace]
});
});
}
/**
* 创建单个接口
* @param sep 分隔符
* @param config 接口配置对象
* @param mock 是否开启mock
* @param debug 是否开启debug模式
* @param mockBaseURL mock接口地址
*/
_apiSingleBuilder({
namespace,
sep = "/",
config = {},
mock = false,
debug = false,
mockBaseURL = ""
}) {
config.forEach(api => {
const { name, desc, params, method, path, cache = true, headers } = api;
let apiname = `${namespace}${sep}${name}`; // 接口调用名称 this.$api['apiname']()
let url = path; // 接口地址
const baseURL = mock ? mockBaseURL : AXIOS_DEFAULT_CONFIG.baseURL; // 接口base地址
debug && assert(name, `${url} :接口name属性不能为空`);
debug &&
assert(url.indexOf("/") === 0, `${url} :接口路径path,首字符应为/`);
Object.defineProperty(this.api, `${apiname}`, {
value(outerParams, outerOptions) {
// let _data = _isEmpty(outerParams) ? params : _pick(_assign({}, params, outerParams), Object.keys(params));
let _data =
_isArray(outerParams) || outerParams instanceof FormData
? outerParams
: _merge({}, params, outerParams);
/*特殊页面,需要对数据做处理*/
if (
(method.toUpperCase() === "POST" ||
method.toUpperCase() === "PUT") &&
(!headers || !headers.hasOwnProperty("Content-Type"))
) {
_data = Qs.stringify(_data);
}
return axios(
_normoalize(
_assign(
{
name,
url,
desc,
baseURL,
method,
headers: headers || null,
cache
},
outerOptions
),
_data
)
);
}
});
});
}
}
/**
* 根据请求类型处理axios参数
* @param options
* @param data
* @returns {*}
* @private
*/
function _normoalize(options, data) {
//处理IE下请求缓存
if (!options.cache) {
options.url = `${options.url}${
options.url.includes("?") ? "&" : "?"
}_=${new Date().getTime()}`;
}
if (options.method === "POST" || options.method === "PUT") {
options.data = data;
} else if (options.method === "GET") {
options.params = data;
}
return options;
}
/**
* 导出接口
*/
export default new Api({
config: API_CONFIG,
...API_DEFAULT_CONFIG
})["api"];
2、 使用axios接口拦截器
import axios from 'axios';
import { AXIOS_DEFAULT_CONFIG } from '@/config';
import {
requestSuccessFunc,
requestFailFunc,
responseSuccessFunc,
responseFailFunc
} from '@/config/interceptor/axios';
let axiosInstance = {};
axiosInstance = axios.create(AXIOS_DEFAULT_CONFIG);
// 注入请求拦截
axiosInstance.interceptors.request.use(requestSuccessFunc, requestFailFunc);
// 注入返回拦截
axiosInstance.interceptors.response.use(responseSuccessFunc, responseFailFunc);
export default axiosInstance;
3、 接口通过一下方式配置
export default [
{
name: "slide",
method: "GET",
desc: "轮播图",
path: "/method/frappe.apis.apps.home.slides"
}
];
四小强之redux的封装
1、reducer的配置
const DEFAULT_STATE = {
homeData: {},
slideData: [],
newsData: [],
newListData: [],
newDetailData: {},
productGroupData: [],
productData: []
};
const USERDATA = {};
export default function(state = DEFAULT_STATE, action = {}) {
switch (action.type) {
case home.HOME_DATA:
return { ...state, ...action.payload };
case home.SLIDE_DATA:
return { ...state, ...action.payload };
case home.NEWS_DATA:
return { ...state, ...action.payload };
case home.NEW_LIST_DATA:
return { ...state, ...action.payload };
case home.NEW_DETAIL_DATA:
return { ...state, ...action.payload };
case home.PRODUCT_GROUP_DATA:
return { ...state, ...action.payload };
case home.PRODUCT_DATA:
return { ...state, ...action.payload };
default:
return state;
}
}
2、actions的配置
import $api from "@/plugins/api";
import { doAction } from "./index";
export const HOME_DATA = "HOME_DATA";
export const NEW_DETAIL_DATA = "NEW_DETAIL_DATA";
export const getHomeData = params =>
doAction(params, "user/userInfo", "HOME_DATA", "homeData");
export const getNewDetailData = (params, url) =>
doAction(
params,
"home/newsDetail",
"NEW_DETAIL_DATA",
"newDetailData",
null,
url
);
同时封装调用接口的逻辑doAction
export function doAction(
params,
url,
dispatchType,
stateData,
model,
replaceUrl
) {
console.log("e", params, url, dispatchType, stateData, model, replaceUrl);
return dispatch => {
return new Promise((resolve, reject) => {
$api[url](params, replaceUrl)
.then(res => {
let newRes;
if (model) {
newRes = model(res.data.display);
} else {
newRes = res.data.display;
}
let reducer = {
type: "",
payload: {}
};
reducer.type = dispatchType;
reducer.payload[stateData] = newRes;
dispatch(reducer);
resolve(newRes);
})
.catch(err => {
reject(err);
});
});
};
}
中间加一层model层,处理后台不规范数据
export function serviceModel(data) {
return data.map((item, index) => {
return {
title: item.plan_name || item.service_project_name,
...item
};
});
}
export function spDetailModel(data) {
let tasks = data.tasks.map((item, index) => {
return {
...item,
description: item.description || ""
};
});
return {
...data,
tasks
};
}
四小强之公共方法的封装
- 封装在utils文件夹中