[react] RN 积累
使用相关
css
pixel point(PT)代表的是逻辑像素而不是实际像素.
可以通过Dimensions 来获取宽高,PixelRatio 获取密度。
React Native中的Flexbox的工作原理和web上的CSS基本一致,当然也存在少许差异。
首先是默认值不同:flexDirection的默认值是column而不是row,alignItems的默认值是stretch而不是flex-start。
justifyContent可以决定其子元素沿着主轴的排列方式。
alignItems可以决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式。
position没有fixed,定位相对于父元素,父元素不用设置position也行?
padding 设置在Text元素上的时候会存在bug。所有padding变成了marginBottom?
position:relative
不可以使用bottom和right?
只有Text和TextInput会继承父组件的background-color
transform:[{scale:2},{skew:'45deg'}]
props.style 可能为 object,number,array
样式的box-sizing似乎是默认border-box的.
Text
<Text>
元素在布局上不同于其它组件:在Text内部的元素不再使用flexbox布局,而是采用文本布局。这意味着内部的元素不再是一个个矩形,而可能会在行末进行折叠。
也就是如果Text元素在Text里边,可以考虑为inline, 如果单独在View里边,那就是Block。
在React Native中,必须把文本节点放在Text组件内。不能直接在下放置一段文本。
也不能直接设置一整颗子树的默认样式。
numberOfLines
用来当文本过长的时候裁剪文本。包括折叠产生的换行在内,总的行数不会超过这个属性的限制。 通常和 ellipsizeMode
配合使用。
numberOfLines 需要放在最外层的Text元素上,且虽然截取了文字但是还是会占用空间.
allowFontScaling
控制字体是否要根据iOS的“文本大小”辅助选项来进行缩放
React Native实际上还是有一部分样式继承的实现,不过仅限于文本标签的子树。
Text不会继承上层View的color,font-size,但是会继承父Text的样式
TextInput
类似Text元素不再使用flexbox布局,而是采用文本布局。
注意有些属性仅在multiline为true或者为false的时候有效。此外,当multiline=false时,为元素的某一个边添加边框样式(例如:borderBottomColor,borderLeftWidth等)将不会生效。为了能够实现效果可以使用一个View来包裹TextInput.
TextInput在安卓上默认有一个底边框,同时会有一些padding。如果要想使其看起来和iOS上尽量一致,则需要设置padding: 0
,同时设置underlineColorAndroid="transparent"
来去掉底边框。
在安卓上如果设置multiline = {true}
,文本默认会垂直居中,可设置textAlignVertical: 'top'
样式来使其居顶显示。
在安卓上长按选择文本会导致windowSoftInputMode
设置变为adjustResize
,这样可能导致绝对定位的元素被键盘给顶起来。要解决这一问题你需要在AndroidManifest.xml
中明确指定合适的windowSoftInputMode值,或是自己监听事件来处理布局变化。
可以动态更改高度来实现类似textarea的效果?
StyleSheet
StyleSheet创建一个样式表后,然后利用ID来引用样式,减少频繁创建新的样式对象。
把多个样式对象,聚合成一个聚合对象。
var styles = StyleSheet.create({
listItem: {
flex: 1,
fontSize: 16,
color: 'white',
},
selectedListItem: {
color: 'green',
},
});
StyleSheet.flatten([styles.listItem, styles.selectedListItem]);
// returns { flex: 1, fontSize: 16, color: 'green' }
另一种写法
const styles = StyleSheet.create({
listItem: {
flex: 1,
fontSize: 16,
color: 'white',
},
selectedListItem: {
color: 'green',
},
});
StyleSheet.flatten(styles.listItem);
// return { flex: 1, fontSize: 16, color: 'white' }
// Simply styles.listItem would return its ID (number)
absoluteFillObject
等价{position: "absolute", left: 0, right: 0, top: 0, bottom: 0}
const styles = StyleSheet.create({
wrapper: {
...StyleSheet.absoluteFillObject,
top: 10,
backgroundColor: 'transparent',
},
});
absoluteFillObject有时不能铺满屏幕,似乎和父元素还是有关系的.
absoluteFill
等价StyleSheet.create({test:{...StyleSheet.absoluteFillObject}}).test
hairlineWidth
const styles = StyleSheet.create({
separator: {
borderBottomColor: '#bbb',
borderBottomWidth: StyleSheet.hairlineWidth,
},
});
定义当前平台上的最细的宽度。可以用作边框或是两个元素间的分隔线。
这一常量始终是一个整数的像素值(线看起来会像头发丝一样细),并会尽量符合当前平台最细的线的标准。然而,不能把它“视为一个常量”,因为不同的平台和不同的屏幕像素密度会导致不同的结果。
Image
决定当组件尺寸和图片尺寸不成比例的时候如何调整图片的大小:
- cover: 在保持图片宽高比的前提下缩放图片,直到宽度和高度都大于等于容器视图的尺寸(如果容器有padding内衬的话,则相应减去)。这样图片完全覆盖甚至超出容器,容器中不留任何空白。
- contain: 在保持图片宽高比的前提下缩放图片,直到宽度和高度都小于等于容器视图的尺寸(如果容器有padding内衬的话,则相应减去)。这样图片完全被包裹在容器中,容器中可能留有空白。
- stretch:拉伸图片且不维持宽高比,直到宽高都刚好填满容器。
- repeat: 重复平铺图片直到填满容器。图片会维持原始尺寸。仅iOS可用。
- center: 居中不拉伸。
ios 图片资源必须是https资源
Image必须设置具体的宽高才能正常显示
Touchable
View不能绑定onPress事件,但是可以用这些替代:
TouchableNativeFeedback和TouchableWithoutFeedback不支持style,但支持onLayout
TouchableHighlight,TouchableOpacity支持style的需要当成View来看待??
TouchableHighlight undelayColor的显示颜色会受到子元素的backgroundColor影响,所以backgroundColor应该尽量只设置在TouchableHighlight上.
TouchableHighlight,TouchableNativeFeedback下级有多级子元素都需要把他们包在一个View里.
Touchable 组件上的onPress可能会阻塞FlatList的滚动
只有touchable支持的事件
- 按钮按下事件:onPress - 按下并松开按钮,会触发该事件(相当于PC的onclick)
- 按钮长按事件:onLongPress - 按住按钮不松开,会触发该事件(长按事件)
- 按钮按下事件:onPressIn - 按下按钮不松开,会触发该事件(相当于PC的onkeydown)
- 按钮松开事件:onPressOut - 按下按钮后松开,或按下按钮后移动手指到按钮区域外,都会触发该事件(相当于PC的onkeyup)
触摸事件总被传送给最上层的组件.
onLayout
onLayout获取元素宽高
Dimensions获取设备宽高
class Main extends Component {
//根View的onLayout回调函数
_onLayout(event) {
//使用大括号是为了限制let结构赋值得到的变量的作用域,因为接来下还要结构解构赋值一次
{
//获取根View的宽高,以及左上角的坐标值
let {x, y, width, height} = event.nativeEvent.layout;
console.log('通过onLayout得到的宽度:' + width);
console.log('通过onLayout得到的高度:' + height);
}
//通过Dimensions API获取屏幕宽高
let {width, height} = Dimensions.get('window');
console.log('通过Dimensions得到的宽度:' + width);
console.log('通过Dimensions得到的高度:' + height);
}
render() {
return (
<View style={styles.flex} onLayout={this._onLayout}>
</View>
);
}
}
元素display为none大部分情况不会触发onLayout,个别组件可能会触发,
display为none与不渲染的区别是什么,未知.
滚动列表 ScrollView
ScrollView会简单粗暴地把所有子元素一次性全部渲染出来。
ScrollView 可以不需要确定父容器高度,只有自容器高度确定也可以
scrollview会影响TextInput的软键盘.
FlatList 会惰性渲染子元素,只在它们将要出现在屏幕中时开始渲染。
SectionList如果列表不需要分组(section),那么可以使用结构更简单的 FlatList。
自定义滚动幅度(分页)(swiper)
<ScrollView
onScroll={eventScrollToEnd}
onMomentumScrollEnd={eventScrollToEnd}
horizontal={true}
snapToInterval={stateViewWidth}
snapToAlignment='start'
decelerationRate='fast'
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
directionalLockEnabled={true}
pinchGestureEnabled={false}
style={{ flex: 1 }}
ref={refSwiperEl}
>
WebView
创建一个原生的WebView,可以用于访问一个网页。WebView可用于音媒体播放.
开发/调试
adb 连接模拟器
D:\Users\qm\AppData\Local\Android\sdk\platform-tools
adb connect 127.0.0.1:62001
windows设置环境变量
ANDROID_HOME
D:\Users\qm\AppData\Local\Android\sdk;
PATH
D:\Users\qm\AppData\Local\Android\sdk\tools;D:\Users\qm\AppData\Local\Android\sdk\platform-tools;
一般来说reset需要删除node_modules和._temp文件夹.
如果是多人合作有时候可能还需要删除package-lock.json文件.
夜神模拟器:
启动模拟器,获取夜神模拟器ip :
127.0.0.1:62025 device
E:\yeshen\Nox\bin
$ nox_adb devices
如果8081端口被封了:
$ adb reverse tcp:5389 tcp:5389
如果报 Counld not connect to development server 错,
摇一摇模拟器,设置Dev Setting,设置Debug server host
react相关
this.setState((prevState, props) => ({
value: this.getRealValue(prevState, props, 'minus')
}), () => {
this.props.onChangeText && this.props.onChangeText(this.state.value);
});
props变更会触发所有组件实例的componentWillReceiveProps,所以要在里面做好判断避免性能问题.
父组件触发render,会触发子组件的render(或者说所有的更新生命周期).
子组件的render不会触发父组件render.
传入的props如果会被子组件回调改变,需要本身是state,不然有可能会不触发.
propTypes可以传递继承
Text.propTypes = {
...RNText.propTypes,
uppercase: PropTypes.bool,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
};
PureComponent相当于在shouldComponentUpdate里做了数据的浅层比较,减少了render次数.
PureComponent可以识别state和props string/number的变化,减少无意义render,但是无法识别array/object的变化.
接入immutable配合使用则可以识别array/object变化,而且可以识别深层变化
但是用immutable的话,需要注意prop-types的类型识别.
例如说默认需要这么写:
oldListTypeChecker: PropTypes.instanceOf(Immutable.List)
也可以使用其他第三方库简化.
Immutable数据不管是set还是merge等操作,都不会变更源数据,需要自己重新赋值.
动画
LayoutAnimation
想做进场动画,可以将动画做在componentDidMount里.
LayoutAnimation.configureNext({
duration: 3000,
create: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
update: {
type: LayoutAnimation.Types.easeInEaseOut,
//property: LayoutAnimation.Properties.height,
}
})
LayoutAnimation就是用于在下一个绘制或者布局周期(render/layout cycle)里处理界面中全部视图的动画的。
例如在更新State之前调用一下LayoutAnimation.sprint()
.
只会执行一次,无法指定触发属性.
transform+LayoutAnimation,可能会出现渲染混乱的问题,暂不知原因.
安卓需要加UIManager.
没有动画结束回调,不占js线程有时候不好控制.
LayoutAnimation 在下一次渲染周期的动画还没执行完执行了其他的setState在安卓里容易crash.
LayoutAnimation 会影响软键盘/光标之类不可控组件的动画
InteractionManager 似乎并不能处理 LayoutAnimation
LayoutAnimation在安卓里容易出问题
Animated
Animated内部子组件使用了LayoutAnimation在安卓里非常容易crash
Animated在安卓的不是特别流畅可以使用useNativeDriver,但是useNativeDriver在安卓里只支持transform和opacity
Animated与touchableOpacity一起用可能会导致动画异常
Animated旋转borderRadius写出的圆,在安卓里动画异常
Animated使用useNativeDriver在安卓里支持transform和opacity
Animated.createAnimatedComponent不能包裹stateless component,因为不能取到ref
触摸事件处理
//父元素设置
onStartShouldSetResponderCapture={() => false}//默认是false,所以并不需要设置,设置成true会停止向下捕获事件
//子元素设置
onStartShouldSetResponder={() => true}//如果不设置为true,只会触发父元素的相同事件
捕获期可通过onStartShouldSetResponderCapture
或 onMoveShouldSetResponderCapture
回调决定是否阻止事件往下级组件传递。
冒泡期可通过onStartShouldSetResponder
或onMoveShouldSetPanResponder
回调决定是否成为响应者。
若上级组件与下级组件都返回true,则下级组件成为当前触摸事件的响应者。(层级越深的组件优先级越高)
组件A、B、C结构
组件A
组件B
组件C
捕获、冒泡机制
sequenceDiagram
A: 是否捕获?若是则停止向下一级传递
A->>B:
B: 是否捕获?若是则停止向下一级传递
B->>C:
C: 是否捕获?若是则停止向下一级传递
C: 是否声明成为响应者?无论是否,都冒泡。
C->>B:
B: 是否声明成为响应者?无论是否,都冒泡。
B->>A:
A: 是否声明成为响应者?
事件生命周期
单点事件
onStartShouldSetResponderCapture 如果父视图想要阻止子视图响应 touch start 事件,它就应该设置这个方法并返回 true。
onStartShouldSetResponder 在用户开始触摸的时候(手指刚刚接触屏幕的瞬间),返回是否愿意成为响应者
onPanResponderGrant 这个视图开始响应触摸事件。此时需要高亮告诉用户正在响应。
onPanResponderStart 触摸事件正式被监视
onPanResponderEnd 触摸事件结束
onPanResponderRelease 在整个触摸事件结束时调用这个函数。
移动事件
onMoveShouldSetResponderCapture 如果父视图想要阻止子视图响应 touch move 事件时,它就应该设置这个方法并返回 true
onMoveShouldSetPanResponder 这个视图想要“认领”这个 touch move 事件吗?每当有 touch move 事件在这个视图中发生,并且这个视图没有被设置为这个 touch move 的响应时,这个函数就会被调用。
onPanResponderGrant 监视器发出通知开始工作
onPanResponderMove 当用户正在屏幕上移动手指时调用这个函数。
异常事件
onPanResponderReject 有一个响应器正处于活跃状态,并且不会向另一个要求响应这个事件的视图释放这个事件。
onPanResponderTerminationRequest 其他某个视图想要成为事件的响应者,并要求这个视图放弃对事件的响应时,就会调用这个函数。如果允许释放响应,就返回true。
onPanResponderTermination 响应被从这个视图上“劫走”了。可能是在调用了 onResponderTerminationRequest 之后,被另一个视图“劫走”了(见 onresponderterminationrequest), 也可能是由于 OS 无条件终止了响应(比如说被 iOS 上的控制中心/消息中心)
View的触摸事件
View组件的pointerEvents属性
用于控制当前视图是否可以作为触控事件的目标。
'auto': 视图可以作为触控事件的目标。
'none': 视图不能作为触控事件的目标。
'box-none': 视图自身不能作为触控事件的目标,但其子视图可以。
'box-only': 视图自身可以作为触控事件的目标,但其子视图不能。
View组件可用的手势
onTouchStart={()=>console.log('start')}
onTouchMove={()=>console.log('move')}
onTouchEnd={()=>console.log('end')}
坑
JSX里''&& 会报错,因为字符串必须放在<Text></Text>
里.
不能在Image里写margin,padding样式,会导致布局混乱.
Text里包裹的Text设置margin,width等属性失效.
Text里包裹View报错.
有的安卓手机会出现Text里字被截断的问题,有可能是系统字体支持的问题,换一种系统字体试试.
iconfont可能形成的不是一个正方形,但是可以通过设置高度调整.
键盘弹起可能会遮罩掉TextInput,可以使用KeyboardAvoidingView 或者 react-native-keyboard-aware-scroll-view解决.
boxShadow在ios和安卓效果不一致,可能需要依赖其他开源组件实现.
安卓里使用 for of 报错
安卓里jsx里没用view包裹元素(如放数组的形式)会报错
自旋转的圆需要写死宽高,否则容易抖动
new Date()在安卓里转换时间的时候很容易crash,因为不支持yyyy-MM-dd
(但是支持yyyy\MM\dd
)
RN里maxWidth和minWidth同时存在时只能生效一个
y轴滚动scrollview嵌套x轴滚动scrollview,会有捕获/冒泡的问题
css里的图片'width:100%'自适应,与背景图的'background-size:cover'有区别,
又与rn里的'resizeMode:cover'有区别