从一个React Native项目回顾几个技术点
前段时间公司碰巧有个较小的项目,我们老大想试下前段时间比较火的React Native。于是我很荣幸作为项目的主程开始我的第一个RN项目。以下是记录了这个项目中用到的技术点。
React Navigation
项目使用了React Navigation作为整个项目的入口和导航栏。由于项目是典型的多标签页设计,所以用了TabNavigator嵌套多个StackNavigator的方式实现。
const DigStack = StackNavigator({
Home: {screen: DigPage},
Login: {screen: Login},
Register: {screen: RegisterPage},
Invite: {screen: InvitePage},
...
}, {
headerMode: 'screen',
});
const MineStack = StackNavigator({
Home: {screen: MinePage},
UserSetting: {screen: UserSetting},
Assets: {screen: AssetsPage},
Login: {screen: Login},
...
}, {
headerMode: 'screen',
});
const MineStack = StackNavigator({
Home: {screen: MinePage},
UserSetting: {screen: UserSetting},
Assets: {screen: AssetsPage},
...
}, {
headerMode: 'screen',
});
export default TabNavi = TabNavigator({
Dig: {
screen: DigStack,
},
Cybertron: {
screen: CybertronStack,
},
Mine: {
screen: MineStack,
}, {
tabBarComponent: TabBarBottom,
tabBarPosition: 'bottom',
swipeEnabled: false,
animationEnabled: false,
navigationOptions: ({navigation}) => ({
tabBarVisible: navigation.state.index === 0
}),
tabBarOptions: {
activeTintColor: '#34c4ff',
inactiveTintColor: '#979797',
style: {backgroundColor: '#ffffff'},
labelStyle: {
fontSize: 14 // 文字大小
}}
})
这样的好处是整个页面的结构和各个页面的关系十分清晰,当项目迭代需要添加新的页面时,添加页面的路由也十分方便。由于是TabNavigator嵌套StackNavigator,navigate后的页面会默认有TabBar,所以要在TabNavigator配置
navigationOptions: ({navigation}) => ({
tabBarVisible: navigation.state.index === 0
})
网络请求
通过Promise.race实现请求超时处理。
首先了解一下Promise.race():
监视多个Promise。接受一个包含需监视的Promise的可迭代对象,并返回一个新的Promise,但一旦来源Promise中有一个被解决,所返回的Promise就会立刻被解决。
通过这样一个类似竞赛的原理,先创建一个会reject的Promise,然后再和网络请求的Promise”比赛”一下,最后用setTimeout进行处理。
//默认10秒超时
const timeout = 10000;
export function post (url, paramsString) {
let timeout_fn = null;
//这是一个可以被reject的promise
let timeout_promise = new Promise(function(resolve, reject) {
timeout_fn = function() {
reject('请求超时,请检查网络');
};
});
//这里使用Promise.race,以最快 resolve 或 reject 的结果来传入后续绑定的回调
let abortable_promise = Promise.race([
fetch_promise(url, paramsString),
timeout_promise
]);
setTimeout(function() {
timeout_fn();
}, timeout);
return abortable_promise ;
}
function fetch_promise(url, paramsString) {
return new Promise((resolve, reject) => {
fetch (BaseURLString + url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: paramsString
})
.then((response) => response.json())
.then((responseJson) => {
console.log(responseJson);
switch (responseJson.code) {
case 1002: User.clearUser(); reject(responseJson.msg); break;
case 1001: reject(responseJson.msg); break;
default: resolve(responseJson.result); break;
}
})
// .catch(error => {
// console.log(error);
// reject(error)
// });
})
}
另外,fetch_promise中会根据返回的json的code处理各种情况,如登录过期需要清空用户信息等。
使用async/await解决异步执行导致的顺序错乱问题
项目中使用了react-native-storage储存用户的信息,每次App启动会在磁盘中读取,读取的请求是异步的,然后根据读取的用户信息再进行网络请求,最后返回特定的信息。那么问题来了,多个异步请求,怎么保证执行顺序呢?就是用async/await。
首先看下async/await的基本语法:
const f = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 2000);
});
};
const testAsync = async () => {
const t = await f();
console.log(t);
};
testAsync();
首先定义了一个函数 f,这个函数返回一个 Promise,并且会延时 2 秒,resolve 并且传入值 123。testAsync 函数在定义时使用了关键字 async,然后函数体中配合使用了 await,最后执行 testAsync。整个程序会在 2 秒后输出 123,也就是说 testAsync 中常量 t 取得了 f 中 resolve 的值,并且通过 await 阻塞了后面代码的执行,直到 f 这个异步函数执行完。
以下是项目中具体的使用:
//User.js
static async getUser () {
let user = {};
await storage.load({
key: 'user',
autoSync: true,
syncInBackground: true
}).then(ret => {
user = ret;
}).catch(err => {
switch (err.name) {
case 'NotFoundError':
break;
case 'ExpiredError':
// TODO
break;
}
});
return user
}
//home.js
User.getUser().then(ret => {
if (ret.user_token) { //获取成功
this.user = ret;
//网络请求
this.getUserInfo();
} else { //获取失败或者为空
//用户信息情况
this.user = {};
//标记用户未登陆
this.setState({isLogin: false});
}
});
几个小技巧
深拷贝
大家都知道深拷贝的重要性,方法也有不少,但在这次项目中,发现最有效的还是利用JSON 深拷贝
JSON.parse(JSON.stringify(obj));
对于一般的需求是可以满足的,但是它有缺点。下例中,可以看到JSON复制会忽略掉值为undefined以及函数表达式。
var obj = {
a: 1,
b: 2,
c: undefined,
sum: function() { return a + b; }
};
var obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2);
//Object {a: 1, b: 2}
px to Dp
UI给的原始尺寸的px值和RN项目中的Dp值是需要转换的,否则换个设备布局又乱了。尝试了网上几个转换方案,以下转换方法最为简单粗暴:
// app 只有竖屏模式,所以可以只获取一次 width
const deviceWidthDp = Dimensions.get('window').width;
// UI 默认给图是 750
const uiWidthPx = 750;
function pxToDp (uiElementPx) {
const transferNumb = uiElementPx * deviceWidthDp / uiWidthPx;
if (transferNumb >= 1) {
// 避免出现循环小数
return Math.ceil(transferNumb);
} else if (Platform.OS === 'android') {
// 如果是安卓,最小为1,避免边框出现锯齿
return 1;
}
return 0.5;
}