ReactNative开发问题及技巧
React Native开发中遇到的一些问题与技巧, 特此摘记^_^
一. 组件相关技巧
Animated
-
Animated.View
中opacity
和pointerEvents
共用时, 经历过opacity
变更后pointerEvent
失效, 原因是react native的性能优化导致的, 可加参数collapsable={false}
阻止优化 -
Animated
中修改Value
值需要使用setValue
方法, 如果直接用this.setState
修改不会成功. (可参考hl-rn-ui中的banner组件)- 错误用法:
this.state = { scrollX: new Animated.Value(0) }; Animated.timing(scrollX, { toValue }).start(() => this.setState({ scrollX: new Animated.Value(0) }));
- 正确用法:
// 用Animated.ValueXY方便之后使用getTranslateTransform方法 this.state = { scrollX: new Animated.ValueXY({ x: 0, y: 0 }) } Animated.timing(scrollX, { toValue }).start(() => { scrollX.setValue({ x: 0, y: 0 }); this.setState({}); });
-
Animated
中是否作为监听者方法中, ios默认从dx与dy都为0时开始执行(HeightPanel组件里记录的ios差异导致的坑, 应该是onMoveShouldSetPanResponder事件中dx与dy值在ios和android上表现不一致的情况, 可以关注下)
ScrollView
- 关闭
ScrollView
阻尼回弹效果:
<ScrollView
alwaysBounceHorizontal={false}
alwaysBounceVertical={false}
bounces={false}
/>
ScrollView
中ios执行onScroll方法会出现异常, 执行结果不符合预期, 需要设置scrollEventThrottle={16}
后才能正常, 但是会有性能消耗
二. 语法使用技巧
1. 动态修改样式
-
在js中动态修改样式
ele.style.height = '100px' ele.setAttribute('height: 100px;');
-
在RN中修改样式
ele.setNativeProps({ height: 100 });
2. TypeScript中运用ReturnType
我们知道setTimeout方法返回的是个整数数字, 但是写成const timer: number = setTimeout(func, num);
时会有ts错误, 此时我们可以写成const timer: ReturnType<typeof setTimeout> = setTimeout(func, num)
, ts的报错用ts的方式解决应该是会更优美.
3. 获取渲染元素的宽高及屏幕坐标
- 利用onLayout事件:
onLayout = (e) => e.nativeEvent.layout
- 主动获取使用
UIManager.measure(elementId, (x, y, width, height, pageX, pageY) => void)
三. 高效程序技巧
减少无用渲染是提高程序性能的主要途径
1. 使用状态容器时应保证高精度颗粒化控制
在使用redux、mobx等状态容器时, 通过关联修饰函数修饰组件时(通常名称为connect或者inject)应尽量保证做到精准颗粒化控制
2. 让每一次刷新都有意义
合理配合shouldComponentUpdate判断当前重渲染是否合理, 如果不合理返回false阻止重新渲染, 也可继承PureComponent类会主动进行浅对比;
state中只放置和刷新关联的元素, 仅仅只是让我们更好的理解程序控制刷新的因子, 主动的刷新控制仅通过setState方法完成(setState伪异步非实时), 因此我们可以一系列的操作修改state内容后, 最后执行一次this.setState({})
即可(仅表明state与刷新无强关联仅为类组件的一个属性, 不做代码参考);
3. 组件入参数据尽量为引用类型
style中直接使用对象的方式和组件事件或者入参为箭头函数的方式, 每次渲染时相对于被使用的组件接收到的数据都是新内存区块的引用, 如果未做优化时则必会发生重新渲染, 即{} !== {}
与(_ => _) !== (_ => _)
问题, 建议style放在StyleSheet中、函数入参提前绑定在上层作用域中.
四. 调试
在我们这的开发模式下, c端调试并不是那么友好, 程序员最讨厌麻烦事了(包括: 连本地、debuger模式不流畅), 可参考用车在调试上的经验, 测试环境通过手机端即可看到打印的日志, 参考代码(取自用车, 进入用车插件点击右上角按钮进入日志面板, 点击all按钮可切换日志类别):
// print.ts
import dayjs from 'dayjs';
import gv from 'Common/globalVariable'; // 全局变量容器
/*
* @description: 日志打印根函数, 日志分类、日志打印在这里实现
* @param {string} logType 日志类型
* @param {boolean} isPush 是否在all里打印
* @param {any[]} params 打印的内容
* @return {void}
*/
function printOrigin(logType: string, isPush: boolean, params: any[]) {
if (!gv.get('canPrint')) return;
gv.setLogArrByLogType(logType);
const logTypeKeys = [logType];
if (isPush && logType !== 'all') logTypeKeys.push('all');
const str = JSON.stringify([dayjs().format('HH:mm:ss SSS'), ...params], undefined, 2).replace(
/"data:image.*"/g,
'"image hide"'
);
logTypeKeys.forEach((key) => {
const printLogArr = gv.get(`printLogObj.${key}`) as string[];
printLogArr.splice(100);
const index = printLogArr.length + 1;
printLogArr.unshift(`${index}. ${str}`);
});
console.debug('MAP-DEBUG::', str);
}
/*
* @description: 提供日志分类注册功能, 可由此方法派生日志命令
* @param {string} logType 日志类型
* @param {boolean} isPush 是否在all里打印
* @return {functino} 派生出来的日志命令
*/
export const generatePrintCommand = (logType: string, isPush: boolean = true) => (...params: any[]) =>
printOrigin(logType, isPush, params);
// 由generatePrintCommand派生, 日志打印
export const print = generatePrintCommand('all');
// 由generatePrintCommand派生, 打印信息类日志
export const printInfo = generatePrintCommand('info');
// 由generatePrintCommand派生, 打印命令日志
export const printCmd = generatePrintCommand('cmd');
// 由generatePrintCommand派生, 打印错误日志
export const printError = generatePrintCommand('error');
export default print;
记录:
import { print, printError, generatePrintCommand } from './print';
const printRender = generatePrintCommand('render');
print('日志集合');
printError('错误日志');
printRender('渲染日志');
显示:
import gv from 'Common/globalVariable'; // 全局变量容器
function render() {
const allLog = gv.get(`printLogObj.all`);
const errorLog = gv.get(`printLogObj.error`);
const renderLog = gv.get(`printLogObj.render`);
return (<View>
<Text>{allLog}</Text>
<Text>{errorLog}</Text>
<Text>{renderLog}</Text>
<View/>)
}
五. 其他
- 生命周期函数
componentWillMount
,componentWillReceiveProps
和componentWillUpdate
已在17版本弃用, 使用getDerivedStateFromProps
和getSnapshotBeforeUpdate
替换 - 避免使用global(window), 可通过闭包的形式封装全局变量, 且财智云中global在iOS和Android实现上存在差异, iOS中会在多个插件生命周期中共用一个global