RN 常用 Api
import React, { useEffect } from 'react'
import {
StyleSheet,
View,
Button,
Alert,
Text,
Dimensions,
useWindowDimensions,
Platform,
Linking,
PixelRatio,
BackHandler,
PermissionsAndroid,
Vibration,
ToastAndroid,
Keyboard,
TextInput,
} from 'react-native'
import { useBackHandler } from '@react-native-community/hooks'
export default () => {
useBackHandler(() => {
return true
})
useEffect(() => {
const subcription = Dimensions.addEventListener(
'change',
(window, screen) => {
console.log(window)
console.log(screen)
}
)
const showSubscription = Keyboard.addListener(
'keyboardDidShow',
onKeyboardShow
)
const hideSubscription = Keyboard.addListener(
'keyboardDidHide',
onKeyboardHide
)
return () => {
subcription.remove()
showSubscription.remove()
hideSubscription.remove()
}
}, [])
const onKeyboardShow = () => {
console.log('键盘出现')
}
const onKeyboardHide = () => {
console.log('键盘隐藏')
}
const onPress = () => {
Keyboard.dismiss()
}
return (
<View style={styles.root}>
<Button title='按钮' onPress={onPress} />
{}
<View
style={[
{
width: 100,
height: 100,
backgroundColor: '#3050ff',
marginTop: 60,
marginLeft: 60,
},
{
transform: [
{ scale: 1.5 },
{ rotateX: '45deg' },
{ rotateZ: '45deg' },
],
},
]}
/>
<TextInput
style={{
width: '100%',
height: 56,
backgroundColor: '#E0E0E0',
}}
/>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
...Platform.select({
android: {
paddingTop: 20,
},
ios: {
paddingTop: 0,
},
default: {
paddingTop: 10,
},
}),
},
view: {
width: '100%',
backgroundColor: 'red',
},
subView: {
width: '100%',
backgroundColor: 'green',
height: PixelRatio.roundToNearestPixel(32.1),
},
})
RN 动画
Animated.View
import React, { useRef } from 'react'
import { StyleSheet, View, Button, Animated } from 'react-native'
export default () => {
const marginLeft = useRef(new Animated.Value(0)).current
return (
<View style={styles.root}>
<Button
title='按钮'
onPress={() => {
Animated.timing(marginLeft, {
toValue: 300,
duration: 500,
useNativeDriver: false,
}).start()
}}
/>
<Animated.View style={[styles.view, { marginLeft: marginLeft }]} />
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
},
view: {
width: 100,
height: 100,
backgroundColor: '#3050ff',
marginTop: 20,
},
})
动画四大类型
import React, { useRef } from 'react'
import { StyleSheet, View, Button, Animated } from 'react-native'
export default () => {
const opacity = useRef(new Animated.Value(0)).current
return (
<View style={styles.root}>
<Button
title='按钮'
onPress={() => {
Animated.timing(opacity, {
toValue: 1,
duration: 1000,
useNativeDriver: false,
}).start()
}}
/>
<Animated.View
style={[
styles.view,
{
transform: [
],
},
{ opacity: opacity },
]}
/>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
},
view: {
width: 100,
height: 100,
backgroundColor: '#3050ff',
marginTop: 60,
marginLeft: 60,
},
})
平移多种属性支持
import React, { useRef } from 'react'
import { StyleSheet, View, Button, Animated } from 'react-native'
export default () => {
const marginLeft = useRef(new Animated.Value(0)).current
return (
<View style={styles.root}>
<Button
title='按钮'
onPress={() => {
Animated.timing(marginLeft, {
toValue: 300,
duration: 500,
useNativeDriver: false,
}).start()
}}
/>
<Animated.View
style={[
styles.view,
{
position: 'absolute',
top: marginLeft,
left: marginLeft,
},
]}
/>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
},
view: {
width: 100,
height: 100,
backgroundColor: '#3050ff',
marginTop: 20,
},
})
三大动画函数
- Animated.decay() : 以一个初始速度开始并且逐渐减慢停止。
import React, { useRef } from 'react'
import { StyleSheet, View, Button, Animated } from 'react-native'
export default () => {
const marginLeft = useRef(new Animated.Value(0)).current
return (
<View style={styles.root}>
<Button
title='按钮'
onPress={() => {
Animated.decay(marginLeft, {
velocity: 1,
deceleration: 0.99,
useNativeDriver: false,
}).start()
}}
/>
<Animated.View style={[styles.view, { marginLeft: marginLeft }]} />
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
},
view: {
width: 100,
height: 100,
backgroundColor: '#3050ff',
marginTop: 20,
},
})
- Animated.spring() : 产生一个基于 Rebound 和 Origami 实现的 Spring 动画。它会在 toValue 值更新的同时跟踪当前的速度状态,以确保动画连贯,默认的弹簧阻尼值为 4,可以通过设置 friction 属性来改变阻尼值,也可以通过设置 tension 属性来改变张力值。
import React, { useRef } from 'react'
import { StyleSheet, View, Button, Animated } from 'react-native'
export default () => {
const marginLeft = useRef(new Animated.Value(0)).current
return (
<View style={styles.root}>
<Button
title='按钮'
onPress={() => {
Animated.spring(marginLeft, {
toValue: 200,
useNativeDriver: false,
bounciness: 25,
speed: 10,
}).start()
}}
/>
<Animated.View style={[styles.view, { marginLeft: marginLeft }]} />
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
},
view: {
width: 100,
height: 100,
backgroundColor: '#3050ff',
marginTop: 20,
},
})
- Animated.timing() : 该动画会在给定的时间内逐步改变一个值。
import React, { useRef } from 'react'
import { StyleSheet, View, Button, Animated, Easing } from 'react-native'
export default () => {
const marginLeft = useRef(new Animated.Value(0)).current
return (
<View style={styles.root}>
<Button
title='按钮'
onPress={() => {
Animated.timing(marginLeft, {
toValue: 300,
duration: 500,
easing: Easing.inOut(Easing.elastic(3)),
useNativeDriver: false,
}).start()
}}
/>
<Animated.View style={[styles.view, { marginLeft: marginLeft }]} />
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
},
view: {
width: 100,
height: 100,
backgroundColor: '#3050ff',
marginTop: 20,
},
})
矢量动画
- Animated.ValueXY: 用于跟踪 2D 值的变化,除了可以像使用 Animated.Value 一样使用它,还可以直接通过设置 x 和 y 属性来进行操作。
import React, { useRef } from 'react'
import { StyleSheet, View, Button, Animated } from 'react-native'
export default () => {
const vector = useRef(new Animated.ValueXY({ x: 0, y: 0 })).current
return (
<View style={styles.root}>
<Button
title='按钮'
onPress={() => {
Animated.timing(vector, {
toValue: { x: 300, y: 400 },
duration: 500,
useNativeDriver: false,
}).start()
}}
/>
<Animated.View
style={[styles.view, { marginLeft: vector.x, marginTop: vector.y }]}
/>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
},
view: {
width: 100,
height: 100,
backgroundColor: '#3050ff',
marginTop: 20,
},
})
组合动画
import React, { useRef } from 'react'
import { StyleSheet, View, Button, Animated } from 'react-native'
export default () => {
const scale = useRef(new Animated.Value(1)).current
const marginLeft = useRef(new Animated.Value(0)).current
const marginTop = useRef(new Animated.Value(0)).current
return (
<View style={styles.root}>
<Button
title='按钮'
onPress={() => {
const moveX = Animated.timing(marginLeft, {
toValue: 200,
duration: 500,
useNativeDriver: false,
})
const moveY = Animated.timing(marginTop, {
toValue: 300,
duration: 500,
useNativeDriver: false,
})
const scaleAnim = Animated.timing(scale, {
toValue: 1.5,
duration: 500,
useNativeDriver: false,
})
Animated.sequence([
moveX,
Animated.delay(1000),
moveY,
Animated.delay(500),
scaleAnim,
]).start()
}}
/>
<Animated.View
style={[
styles.view,
{
transform: [
{ scale: scale },
{ translateX: marginLeft },
{ translateY: marginTop },
],
},
]}
/>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
},
view: {
width: 100,
height: 100,
backgroundColor: '#3050ff',
marginTop: 20,
},
})
跟随动画难题
import React, { useState, useRef } from 'react'
import { StyleSheet, View, ScrollView, Animated } from 'react-native'
const colors = ['red', 'green', 'blue', 'yellow', 'orange']
export default () => {
const scrollY = useRef(new Animated.Value(0)).current
const viewList = () => {
const array = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
]
return (
<>
{array.map((item, index) => (
<View
key={item}
style={{
width: 60,
height: 100,
backgroundColor: colors[index % 5],
}}
/>
))}
</>
)
}
return (
<View style={styles.root}>
<View style={styles.leftLayout}>
<Animated.View
style={{
width: 60,
transform: [
{ translateY: Animated.multiply(-1, scrollY) },
],
}}
>
{viewList()}
</Animated.View>
</View>
<View style={styles.rightLayout}>
<Animated.ScrollView
showsVerticalScrollIndicator={false}
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: { y: scrollY },
},
},
],
{ useNativeDriver: true }
)}
>
{viewList()}
</Animated.ScrollView>
</View>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
flexDirection: 'row',
justifyContent: 'center',
},
leftLayout: {
width: 60,
backgroundColor: '#00FF0030',
flexDirection: 'column',
},
rightLayout: {
width: 60,
height: '100%',
backgroundColor: '#0000FF30',
marginLeft: 100,
},
})
优化 Modal 动画
import React, { useState, useRef } from 'react'
import {
StyleSheet,
View,
Modal,
Text,
Button,
SectionList,
TouchableOpacity,
Image,
Animated,
Dimensions,
} from 'react-native'
import icon_close_modal from '../assets/images/icon_close_modal.png'
import { SectionData } from '../constants/Data'
const { height: WINDOW_HEIGHT } = Dimensions.get('window')
export default () => {
const [visible, setVisible] = useState(false)
const marginTop = useRef(new Animated.Value(WINDOW_HEIGHT)).current
const showModal = () => {
setVisible(true)
Animated.timing(marginTop, {
toValue: 0,
duration: 500,
useNativeDriver: false,
}).start()
}
const hideModal = () => {
Animated.timing(marginTop, {
toValue: WINDOW_HEIGHT,
duration: 500,
useNativeDriver: false,
}).start(() => {
setVisible(false)
})
}
const renderItem = ({ item, index, section }) => {
return <Text style={styles.txt}>{item}</Text>
}
const ListHeader = (
<View style={styles.header}>
<Text style={styles.extraTxt}>列表头部</Text>
<TouchableOpacity style={styles.closeButton} onPress={() => hideModal()}>
<Image style={styles.closeImg} source={icon_close_modal} />
</TouchableOpacity>
</View>
)
const ListFooter = (
<View style={[styles.header, styles.footer]}>
<Text style={styles.extraTxt}>列表尾部</Text>
</View>
)
const renderSectionHeader = ({ section }) => {
return <Text style={styles.sectionHeaderTxt}>{section.type}</Text>
}
return (
<View style={styles.root}>
<Button title='按钮' onPress={() => showModal()} />
<Modal
visible={visible}
onRequestClose={() => hideModal()}
transparent={true}
statusBarTranslucent={true}
animationType='fade'
>
<View style={styles.container}>
<Animated.View
style={[
styles.contentView,
{
marginTop: marginTop,
},
]}
>
<SectionList
style={styles.sectionList}
contentContainerStyle={styles.containerStyle}
sections={SectionData}
renderItem={renderItem}
keyExtractor={(item, index) => `${item}-${index}`}
showsVerticalScrollIndicator={false}
ListHeaderComponent={ListHeader}
ListFooterComponent={ListFooter}
renderSectionHeader={renderSectionHeader}
ItemSeparatorComponent={() => <View style={styles.separator} />}
stickySectionHeadersEnabled={true}
/>
</Animated.View>
</View>
</Modal>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
paddingHorizontal: 16,
},
container: {
width: '100%',
height: '100%',
backgroundColor: '#00000060',
},
contentView: {
width: '100%',
height: '100%',
paddingTop: '30%',
},
sectionList: {
width: '100%',
height: '80%',
},
txt: {
width: '100%',
height: 56,
fontSize: 20,
color: '#333333',
textAlignVertical: 'center',
paddingLeft: 16,
},
containerStyle: {
backgroundColor: '#F5F5F5',
},
header: {
width: '100%',
height: 48,
backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
},
footer: {
backgroundColor: '#ff000030',
},
extraTxt: {
fontSize: 20,
color: '#666666',
textAlignVertical: 'center',
},
sectionHeaderTxt: {
width: '100%',
height: 36,
backgroundColor: '#DDDDDD',
textAlignVertical: 'center',
paddingLeft: 16,
fontSize: 20,
color: '#333333',
fontWeight: 'bold',
},
separator: {
width: '100%',
height: 2,
backgroundColor: '#D0D0D0',
},
closeButton: {
width: 24,
height: 24,
position: 'absolute',
right: 16,
},
closeImg: {
width: 24,
height: 24,
},
})
LayoutAnimation
if (Platform.OS === 'android') {
if (UIManager.setLayoutAnimationEnabledExperimental) {
console.log('enable ...')
UIManager.setLayoutAnimationEnabledExperimental(true)
}
}
import React, { useState } from 'react'
import {
StyleSheet,
View,
Button,
LayoutAnimation,
Image,
Text,
} from 'react-native'
import icon_avatar from '../assets/images/default_avatar.png'
export default () => {
const [showView, setShowView] = useState(false)
const [showRight, setShowRight] = useState(false)
return (
<View style={styles.root}>
<Button
title='按钮'
onPress={() => {
LayoutAnimation.spring()
setShowRight(true)
}}
/>
{}
<View
style={[
styles.view,
{ flexDirection: showRight ? 'row-reverse' : 'row' },
]}
>
<Image style={styles.img} source={icon_avatar} />
<Text style={styles.txt}>这是一行自我介绍的文本</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
},
view: {
width: '100%',
height: 100,
backgroundColor: '#F0F0F0',
marginTop: 20,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
},
img: {
width: 64,
height: 64,
borderRadius: 32,
},
txt: {
fontSize: 20,
color: '#303030',
fontWeight: 'bold',
marginHorizontal: 20,
},
})
动画练习(动画菜单)
import React, { useRef, useState, useEffect } from 'react'
import {
StyleSheet,
View,
Image,
Text,
Animated,
Easing,
TouchableOpacity,
} from 'react-native'
import icon_gift from '../assets/images/icon_gift.png'
import icon_mine from '../assets/images/icon_mine.png'
import icon_home from '../assets/images/icon_home.png'
import icon_show from '../assets/images/icon_show.png'
export default () => {
const width1 = useRef(new Animated.Value(200)).current
const width2 = useRef(new Animated.Value(64)).current
const width3 = useRef(new Animated.Value(64)).current
const width4 = useRef(new Animated.Value(64)).current
const [index, setIndex] = useState(0)
useEffect(() => {
anim1(index === 0)
anim2(index === 1)
anim3(index === 2)
anim4(index === 3)
}, [index])
const anim1 = (isOpen) => {
Animated.timing(width1, {
toValue: isOpen ? 200 : 64,
duration: isOpen ? 500 : 300,
easing: isOpen ? Easing.elastic(3) : Easing.ease,
useNativeDriver: false,
}).start()
}
const anim2 = (isOpen) => {
Animated.timing(width2, {
toValue: isOpen ? 200 : 64,
duration: isOpen ? 500 : 300,
easing: isOpen ? Easing.elastic(3) : Easing.ease,
useNativeDriver: false,
}).start()
}
const anim3 = (isOpen) => {
Animated.timing(width3, {
toValue: isOpen ? 200 : 64,
duration: isOpen ? 500 : 300,
easing: isOpen ? Easing.elastic(3) : Easing.ease,
useNativeDriver: false,
}).start()
}
const anim4 = (isOpen) => {
Animated.timing(width4, {
toValue: isOpen ? 200 : 64,
duration: isOpen ? 500 : 300,
easing: isOpen ? Easing.elastic(3) : Easing.ease,
useNativeDriver: false,
}).start()
}
return (
<View style={styles.root}>
<TouchableOpacity
activeOpacity={0.8}
onPress={() => {
setIndex(0)
}}
>
<Animated.View
style={[
styles.view,
{ width: width1, opacity: index === 0 ? 1 : 0.75 },
]}
>
<Image style={styles.img} source={icon_home} />
<Text style={styles.txt} numberOfLines={1} ellipsizeMode='clip'>
首页推荐
</Text>
<View style={styles.dot} />
</Animated.View>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.8}
onPress={() => {
setIndex(1)
}}
>
<Animated.View
style={[
styles.view,
{ width: width2, opacity: index === 1 ? 1 : 0.75 },
]}
>
<Image style={styles.img} source={icon_show} />
<Text style={styles.txt} numberOfLines={1} ellipsizeMode='clip'>
热门直播
</Text>
<View style={styles.dot} />
</Animated.View>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.8}
onPress={() => {
setIndex(2)
}}
>
<Animated.View
style={[
styles.view,
{ width: width3, opacity: index === 2 ? 1 : 0.75 },
]}
>
<Image style={styles.img} source={icon_gift} />
<Text style={styles.txt} numberOfLines={1} ellipsizeMode='clip'>
我的礼物
</Text>
<View style={styles.dot} />
</Animated.View>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.8}
onPress={() => {
setIndex(3)
}}
>
<Animated.View
style={[
styles.view,
{ width: width4, opacity: index === 3 ? 1 : 0.75 },
]}
>
<Image style={styles.img} source={icon_mine} />
<Text style={styles.txt} numberOfLines={1} ellipsizeMode='clip'>
个人信息
</Text>
<View style={styles.dot} />
</Animated.View>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
flexDirection: 'column',
justifyContent: 'center',
},
view: {
height: 60,
flexDirection: 'row',
alignItems: 'center',
marginVertical: 16,
borderTopRightRadius: 28,
borderBottomRightRadius: 28,
backgroundColor: '#2030ff',
paddingLeft: 16,
overflow: 'hidden',
},
img: {
width: 32,
height: 32,
tintColor: 'white',
},
txt: {
fontSize: 18,
color: '#ffffffD0',
marginLeft: 16,
},
dot: {
width: 10,
height: 10,
backgroundColor: '#20f020',
marginLeft: 28,
borderRadius: 5,
},
})
Context 使用
- React.createContext: 创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
import { createContext } from 'react'
export const ThemeContext = createContext<string>('dark')
import React, { useState } from 'react'
import { View, Button } from 'react-native'
import { ThemeContext } from './ThemeContext'
import PageView from './PageView'
export default () => {
const [theme, setTheme] = useState('dark')
return (
<ThemeContext.Provider value={theme}>
<Button
title='切换主题'
onPress={() => {
setTheme((state) => {
if (state === 'dark') {
return 'light'
} else {
return 'dark'
}
})
}}
/>
<View style={{ width: '100%' }}>
<PageView />
</View>
</ThemeContext.Provider>
)
}
import React from 'react'
import { View } from 'react-native'
import Header from './Header'
export default () => {
return (
<View>
<Header />
</View>
)
}
import React, { useContext } from 'react'
import { StyleSheet, View, Image, Text } from 'react-native'
import icon_avatar from '../assets/images/default_avatar.png'
import { ThemeContext } from './ThemeContext'
export default () => {
const theme = useContext(ThemeContext)
const styles = theme === 'dark' ? darkStyles : lightStyles
return (
<View style={styles.content}>
<Image style={styles.img} source={icon_avatar} />
<Text style={styles.txt}>个人信息介绍</Text>
<View style={styles.infoLayout}>
<Text style={styles.infoTxt}>
各位产品经理大家好,我是个人开发者张三,我学习RN两年半了,我喜欢安卓、RN、Flutter。
</Text>
</View>
</View>
)
}
const darkStyles = StyleSheet.create({
content: {
width: '100%',
height: '100%',
backgroundColor: '#353535',
flexDirection: 'column',
alignItems: 'center',
paddingHorizontal: 16,
paddingTop: 64,
},
img: {
width: 96,
height: 96,
borderRadius: 48,
borderWidth: 4,
borderColor: '#ffffffE0',
},
txt: {
fontSize: 24,
color: 'white',
fontWeight: 'bold',
marginTop: 32,
},
infoLayout: {
width: '90%',
padding: 16,
backgroundColor: '#808080',
borderRadius: 12,
marginTop: 24,
},
infoTxt: {
fontSize: 16,
color: 'white',
},
})
const lightStyles = StyleSheet.create({
content: {
width: '100%',
height: '100%',
backgroundColor: '#fafafa',
flexDirection: 'column',
alignItems: 'center',
paddingHorizontal: 16,
paddingTop: 64,
},
img: {
width: 96,
height: 96,
borderRadius: 48,
borderWidth: 4,
borderColor: '#00000080',
},
txt: {
fontSize: 24,
color: '#333333',
fontWeight: 'bold',
marginTop: 32,
},
infoLayout: {
width: '90%',
padding: 16,
backgroundColor: '#EAEAEA',
borderRadius: 12,
marginTop: 24,
},
infoTxt: {
fontSize: 16,
color: '#666666',
},
})
Hoc 使用
- Hoc: 高阶组件,是参数为组件,返回值为新组件的函数。
import React, { useEffect } from 'react'
import { TouchableOpacity, Image, StyleSheet } from 'react-native'
type IReactComponent =
| React.ClassicComponentClass
| React.ComponentClass
| React.FunctionComponent
| React.ForwardRefExoticComponent<any>
import icon_add from '../assets/images/icon_add.png'
export default <T extends IReactComponent>(OriginView: T, type: string): T => {
const HOCView = (props: any) => {
useEffect(() => {
reportDeviceInfo()
}, [])
const reportDeviceInfo = () => {
const deviceInfo = {
deviceId: 1,
deviceName: '',
modal: '',
storage: 0,
ip: '',
}
}
return (
<>
<OriginView {...props} />
<TouchableOpacity
style={styles.addButton}
onPress={() => {
console.log(`onPress ...`)
}}
>
<Image style={styles.addImg} source={icon_add} />
</TouchableOpacity>
</>
)
}
return HOCView as T
}
const styles = StyleSheet.create({
addButton: {
position: 'absolute',
bottom: 80,
right: 28,
},
addImg: {
width: 54,
height: 54,
resizeMode: 'contain',
},
})
import React, { useEffect } from 'react'
import { StyleSheet, View, Image, Text } from 'react-native'
import icon_avatar from '../assets/images/default_avatar.png'
import withFloatButton from './withFloatButton'
export default withFloatButton(() => {
const styles = darkStyles
return (
<View style={styles.content}>
<Image style={styles.img} source={icon_avatar} />
<Text style={styles.txt}>个人信息介绍</Text>
<View style={styles.infoLayout}>
<Text style={styles.infoTxt}>
各位产品经理大家好,我是个人开发者张三,我学习RN两年半了,我喜欢安卓、RN、Flutter,Thank
you!。
</Text>
</View>
</View>
)
}, 'InfoView')
const darkStyles = StyleSheet.create({
content: {
width: '100%',
height: '100%',
backgroundColor: '#353535',
flexDirection: 'column',
alignItems: 'center',
paddingHorizontal: 16,
paddingTop: 64,
},
img: {
width: 96,
height: 96,
borderRadius: 48,
borderWidth: 4,
borderColor: '#ffffffE0',
},
txt: {
fontSize: 24,
color: 'white',
fontWeight: 'bold',
marginTop: 32,
},
infoLayout: {
width: '90%',
padding: 16,
backgroundColor: '#808080',
borderRadius: 12,
marginTop: 24,
},
infoTxt: {
fontSize: 16,
color: 'white',
},
})
memo 与 性能优化
React.memo
: 用于函数组件的性能优化,只有在组件的 props 发生改变时才会重新渲染组件,否则使用上一次的渲染结果。useMemo
: 用于函数组件的性能优化,依赖的值发生变化时才会重新计算值。useCallback
: 用于函数组件的性能优化,返回一个函数,依赖的值发生变化时才会重新计算值。shouldComponentUpdate
: 用于类组件的性能优化,返回 true
时才会重新渲染组件,否则使用上一次的渲染结果。PureComponent
: 用于类组件的性能优化,浅比较 props
和 state
,只有发生改变时才会重新渲染组件,否则使用上一次的渲染结果。React.memo
与 PureComponent
的区别:React.memo
只能用于函数组件,PureComponent
只能用于类组件,React.memo
与 PureComponent
都是浅比较,React.memo
可以自定义比较函数。React.memo
与 useMemo
的区别:React.memo
用于组件,useMemo
用于值,React.memo
依赖的值发生变化时才会重新渲染组件,useMemo
依赖的值发生变化时才会重新计算值。React.memo
与 useCallback
的区别:React.memo
用于组件,useCallback
用于函数,React.memo
依赖的值发生变化时才会重新渲染组件,useCallback
依赖的值发生变化时才会重新计算值。React.memo
与 shouldComponentUpdate
的区别:React.memo 用于组件,shouldComponentUpdate
用于类组件,React.memo
依赖的值发生变化时才会重新渲染组件,shouldComponentUpdate
返回 true
时才会重新渲染组件。
import React, { useState } from 'react'
import { View, Button } from 'react-native'
import InfoView from './InfoView'
import InfoView2 from './InfoView2'
import ConsumeList from './ConsumeList'
import { avatarUri } from '../constants/Uri'
export default () => {
const [info, setInfo] = useState<UserInfo>({
avatar: '',
name: '',
desc: '',
})
return (
<View style={{ width: '100%' }}>
{}
{}
{}
<ConsumeList />
</View>
)
}
import React from 'react'
import { StyleSheet, View, Image, Text } from 'react-native'
type Props = {
info: UserInfo
}
export default React.memo(
(props: Props) => {
const { info } = props
console.log('render ...')
return (
<View style={darkStyles.content}>
<Image style={darkStyles.img} source={{ uri: info.avatar }} />
<Text style={darkStyles.txt}>{info.name}</Text>
<View style={darkStyles.infoLayout}>
<Text style={darkStyles.infoTxt}>{info.desc}</Text>
</View>
</View>
)
},
(preProps: Props, nextProps: Props) =>
JSON.stringify(preProps.info) === JSON.stringify(nextProps.info)
)
const darkStyles = StyleSheet.create({
content: {
width: '100%',
height: '100%',
backgroundColor: '#353535',
flexDirection: 'column',
alignItems: 'center',
paddingHorizontal: 16,
paddingTop: 64,
},
img: {
width: 96,
height: 96,
borderRadius: 48,
borderWidth: 4,
borderColor: '#ffffffE0',
},
txt: {
fontSize: 24,
color: 'white',
fontWeight: 'bold',
marginTop: 32,
},
infoLayout: {
width: '90%',
padding: 16,
backgroundColor: '#808080',
borderRadius: 12,
marginTop: 24,
},
infoTxt: {
fontSize: 16,
color: 'white',
},
})
import React from 'react'
import { StyleSheet, View, Image, Text } from 'react-native'
type Props = {
info: UserInfo
}
export default class InfoView2 extends React.Component<Props, any> {
constructor(props: Props) {
super(props)
}
shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
return JSON.stringify(nextProps.info) !== JSON.stringify(this.props.info)
}
render(): React.ReactNode {
console.log('render ...')
const { info } = this.props
return (
<View style={darkStyles.content}>
<Image style={darkStyles.img} source={{ uri: info.avatar }} />
<Text style={darkStyles.txt}>{info.name}</Text>
<View style={darkStyles.infoLayout}>
<Text style={darkStyles.infoTxt}>{info.desc}</Text>
</View>
</View>
)
}
}
const darkStyles = StyleSheet.create({
content: {
width: '100%',
height: '100%',
backgroundColor: '#353535',
flexDirection: 'column',
alignItems: 'center',
paddingHorizontal: 16,
paddingTop: 64,
},
img: {
width: 96,
height: 96,
borderRadius: 48,
borderWidth: 4,
borderColor: '#ffffffE0',
},
txt: {
fontSize: 24,
color: 'white',
fontWeight: 'bold',
marginTop: 32,
},
infoLayout: {
width: '90%',
padding: 16,
backgroundColor: '#808080',
borderRadius: 12,
marginTop: 24,
},
infoTxt: {
fontSize: 16,
color: 'white',
},
})
import React, { useState, useMemo, useCallback } from 'react'
import {
View,
Button,
StyleSheet,
FlatList,
Switch,
Text,
TouchableOpacity,
} from 'react-native'
import { ListData, ListData2 } from '../constants/Data'
import { TypeColors } from '../constants/Data'
export default () => {
const [data, setData] = useState<any>(ListData)
const [showType, setShowType] = useState<boolean>(true)
const totalAmountView = useMemo(() => {
const total = data
.map((item: any) => item.amount)
.reduce((pre: number, cur: number) => pre + cur)
console.log('重新渲染合计')
return (
<View style={styles.totalLayout}>
<Text style={styles.totalTxt}>{total}</Text>
<Text style={styles.totalTxt}>合计:</Text>
</View>
)
}, [data])
const onItemPress = useCallback(
(item: any, index: number) => () => {
console.log(`点击第${item.index}行`)
},
[]
)
const renderItem = ({ item, index }: any) => {
const styles = StyleSheet.create({
itemLayout: {
width: '100%',
padding: 16,
flexDirection: 'column',
borderBottomWidth: 1,
borderBottomColor: '#E0E0E0',
},
labelRow: {
width: '100%',
flexDirection: 'row',
alignItems: 'center',
},
valueRow: {
marginTop: 10,
},
labelTxt: {
flex: 1,
fontSize: 14,
color: '#666',
},
first: {
flex: 0.4,
},
second: {
flex: 0.3,
},
last: {
flex: 0.6,
},
valueTxt: {
flex: 1,
fontSize: 18,
color: '#333',
fontWeight: 'bold',
},
typeLayout: {
flex: 0.3,
},
typeTxt: {
width: 20,
height: 20,
textAlign: 'center',
textAlignVertical: 'center',
color: 'white',
borderRadius: 4,
fontWeight: 'bold',
},
})
return (
<TouchableOpacity
style={styles.itemLayout}
onPress={onItemPress(item, index)}
>
<View style={styles.labelRow}>
<Text style={[styles.labelTxt, styles.first]}>序号</Text>
{showType && (
<Text style={[styles.labelTxt, styles.second]}>类型</Text>
)}
<Text style={styles.labelTxt}>消费名称</Text>
<Text style={[styles.labelTxt, styles.last]}>消费金额</Text>
</View>
<View style={[styles.labelRow, styles.valueRow]}>
<Text style={[styles.valueTxt, styles.first]}>{item.index}</Text>
{showType && (
<View style={styles.typeLayout}>
<Text
style={[
styles.typeTxt,
{ backgroundColor: TypeColors[item.type] },
]}
>
{item.type}
</Text>
</View>
)}
<Text style={styles.valueTxt}>{item.name}</Text>
<Text style={[styles.valueTxt, styles.last]}>{item.amount}</Text>
</View>
</TouchableOpacity>
)
}
return (
<View style={styles.root}>
<View style={styles.titleLayout}>
<Text style={styles.titleTxt}>消费记账单</Text>
<Switch
style={styles.switch}
value={showType}
onValueChange={(value) => setShowType(value)}
/>
<Button
title='切换数据'
onPress={() => {
setData(ListData2)
}}
/>
</View>
<FlatList
data={data}
keyExtractor={(item, index) => `${item.index}-${item.name}`}
renderItem={renderItem}
/>
{}
{totalAmountView}
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
},
titleLayout: {
width: '100%',
height: 56,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
titleTxt: {
fontSize: 18,
color: '#333',
fontWeight: 'bold',
},
totalLayout: {
width: '100%',
height: 60,
flexDirection: 'row-reverse',
borderTopWidth: 1,
borderTopColor: '#c0c0c0',
alignItems: 'center',
paddingHorizontal: 16,
},
totalTxt: {
fontSize: 20,
color: '#333',
fontWeight: 'bold',
},
switch: {
position: 'absolute',
right: 16,
},
})
type UserInfo = {
avatar: string
name: string
desc: string
}
Ref 转发
- Ref: 用于函数组件中获取组件实例,使用
ForwardRef
可以将 Ref
传递给子组件 - Ref: 用户类组件中获取组件实例
import React, { useRef } from 'react'
import { StyleSheet, View, Button, TextInput } from 'react-native'
import CustomInput2 from './CustomInput2'
export default () => {
const inputRef = useRef<CustomInput2>(null)
return (
<View style={styles.root}>
<Button
title='聚焦'
onPress={() => {
inputRef.current?.customFocus()
}}
/>
<Button
title='失焦'
onPress={() => {
inputRef.current?.customBlur()
inputRef.current?.customXXX()
}}
/>
{}
<CustomInput2 ref={inputRef} />
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
paddingHorizontal: 20,
paddingTop: 64,
},
})
import React, { useState, useRef, forwardRef, useImperativeHandle } from 'react'
import {
StyleSheet,
View,
Image,
Text,
TextInput,
LayoutAnimation,
TouchableOpacity,
} from 'react-native'
import icon_error from '../assets/images/icon_error.png'
import icon_right from '../assets/images/icon_right.png'
import icon_question from '../assets/images/icon_question.webp'
import icon_delete from '../assets/images/icon_delete.png'
export interface CustomInputRef {
customFocus: () => void
customBlur: () => void
}
export default forwardRef((props, ref) => {
const inputRef = useRef<TextInput>(null)
const [value, setValue] = useState<string>('')
const customFocus = () => {
inputRef.current?.focus()
}
const customBlur = () => {
inputRef.current?.blur()
}
useImperativeHandle(ref, () => {
return {
customFocus,
customBlur,
}
})
return (
<View style={styles.root}>
<View
style={[
styles.inputWrap,
{
borderColor: !value
? '#888'
: value?.length === 11
? '#00CD00'
: '#ff3050',
},
]}
>
<TextInput
ref={inputRef}
style={styles.input}
value={value}
keyboardType='number-pad'
onChangeText={(value) => {
LayoutAnimation.spring()
setValue(value)
}}
maxLength={11}
/>
{!!value && (
<TouchableOpacity
style={styles.deleteButton}
onPress={() => {
LayoutAnimation.spring()
setValue('')
}}
>
<Image style={styles.deleteImg} source={icon_delete} />
</TouchableOpacity>
)}
</View>
<View style={styles.tipsLayout}>
{!value ? (
<>
<Image style={styles.tipImg} source={icon_question} />
<Text style={styles.tipsTxt}>请输入您的手机号</Text>
</>
) : value.length === 11 ? (
<>
<Image style={styles.tipImgRight} source={icon_right} />
<Text style={styles.tipsTxtRight}>输入正确,可进行提交</Text>
</>
) : (
<>
<Image style={styles.tipImgError} source={icon_error} />
<Text style={styles.tipsTxtError}>格式错误,请输入正确手机号</Text>
</>
)}
</View>
</View>
)
})
const styles = StyleSheet.create({
root: {
width: '100%',
flexDirection: 'column',
},
input: {
width: '100%',
height: 56,
backgroundColor: 'transparent',
paddingHorizontal: 16,
fontSize: 22,
color: '#333',
},
inputWrap: {
width: '100%',
borderWidth: 2,
borderRadius: 12,
flexDirection: 'row',
alignItems: 'center',
},
tipsLayout: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 6,
paddingHorizontal: 6,
},
tipImg: {
width: 22,
height: 22,
resizeMode: 'contain',
tintColor: '#888',
},
tipsTxt: {
fontSize: 15,
color: '#666',
marginLeft: 6,
fontWeight: 'bold',
},
tipImgRight: {
width: 18,
height: 18,
resizeMode: 'contain',
tintColor: '#00CD00',
},
tipsTxtRight: {
fontSize: 15,
color: '#00CD00',
marginLeft: 6,
fontWeight: 'bold',
},
tipImgError: {
width: 18,
height: 18,
resizeMode: 'contain',
tintColor: '#ff3050',
},
tipsTxtError: {
fontSize: 15,
color: '#ff3050',
marginLeft: 6,
fontWeight: 'bold',
},
deleteButton: {
position: 'absolute',
right: 16,
},
deleteImg: {
width: 24,
height: 24,
resizeMode: 'contain',
borderRadius: 12,
},
})
import React from 'react'
import {
StyleSheet,
View,
Image,
Text,
TextInput,
LayoutAnimation,
TouchableOpacity,
} from 'react-native'
import icon_error from '../assets/images/icon_error.png'
import icon_right from '../assets/images/icon_right.png'
import icon_question from '../assets/images/icon_question.webp'
import icon_delete from '../assets/images/icon_delete.png'
export default class CustomInput extends React.Component {
inputRef = React.createRef<TextInput>()
state = {
value: '',
}
constructor(props: any) {
super(props)
this.state = {
value: '',
}
}
customFocus = () => {
this.inputRef.current?.focus()
}
customBlur = () => {
this.inputRef.current?.blur()
}
customXXX = () => {
console.log('customXXX ...')
}
render() {
const { value } = this.state
return (
<View style={styles.root}>
<View
style={[
styles.inputWrap,
{
borderColor: !value
? '#888'
: value?.length === 11
? '#00CD00'
: '#ff3050',
},
]}
>
<TextInput
ref={this.inputRef}
style={styles.input}
value={value}
keyboardType='number-pad'
onChangeText={(value) => {
LayoutAnimation.spring()
this.setState({
value,
})
}}
maxLength={11}
/>
{!!value && (
<TouchableOpacity
style={styles.deleteButton}
onPress={() => {
LayoutAnimation.spring()
this.setState({
value: '',
})
}}
>
<Image style={styles.deleteImg} source={icon_delete} />
</TouchableOpacity>
)}
</View>
<View style={styles.tipsLayout}>
{!value ? (
<>
<Image style={styles.tipImg} source={icon_question} />
<Text style={styles.tipsTxt}>请输入您的手机号</Text>
</>
) : value.length === 11 ? (
<>
<Image style={styles.tipImgRight} source={icon_right} />
<Text style={styles.tipsTxtRight}>输入正确,可进行提交</Text>
</>
) : (
<>
<Image style={styles.tipImgError} source={icon_error} />
<Text style={styles.tipsTxtError}>
格式错误,请输入正确手机号
</Text>
</>
)}
</View>
</View>
)
}
}
const styles = StyleSheet.create({
root: {
width: '100%',
flexDirection: 'column',
},
input: {
width: '100%',
height: 56,
backgroundColor: 'transparent',
paddingHorizontal: 16,
fontSize: 22,
color: '#333',
},
inputWrap: {
width: '100%',
borderWidth: 2,
borderRadius: 12,
flexDirection: 'row',
alignItems: 'center',
},
tipsLayout: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 6,
paddingHorizontal: 6,
},
tipImg: {
width: 22,
height: 22,
resizeMode: 'contain',
tintColor: '#888',
},
tipsTxt: {
fontSize: 15,
color: '#666',
marginLeft: 6,
fontWeight: 'bold',
},
tipImgRight: {
width: 18,
height: 18,
resizeMode: 'contain',
tintColor: '#00CD00',
},
tipsTxtRight: {
fontSize: 15,
color: '#00CD00',
marginLeft: 6,
fontWeight: 'bold',
},
tipImgError: {
width: 18,
height: 18,
resizeMode: 'contain',
tintColor: '#ff3050',
},
tipsTxtError: {
fontSize: 15,
color: '#ff3050',
marginLeft: 6,
fontWeight: 'bold',
},
deleteButton: {
position: 'absolute',
right: 16,
},
deleteImg: {
width: 24,
height: 24,
resizeMode: 'contain',
borderRadius: 12,
},
})
桥接原生
实现 JS 调用原生方法
import React from 'react'
import {
StyleSheet,
View,
Button,
NativeModules,
Image,
Text,
} from 'react-native'
import NativeInfoView from './NativeInfoView'
import NativeInfoViewGroup from './NativeInfoViewGroup'
import { avatarUri } from '../constants/Uri'
export default () => {
return (
<View style={styles.root}>
<Button
title='调用原生方法'
onPress={() => {
const { App } = NativeModules
const { versionName, versionCode } = App
console.log(`versionName=${versionName}, versionCode=${versionCode}`)
}}
/>
{}
<NativeInfoViewGroup>
<View style={styles.content}>
<Image style={styles.avatarImg} source={{ uri: avatarUri }} />
<View style={styles.nameLayout}>
<Text style={styles.nameTxt}>尼古拉斯·段坤</Text>
<Text style={styles.descTxt}>
各位产品经理大家好,我是个人开发者张三,我学习RN两年半了,我喜欢安卓、RN、Flutter,Thank
you!。
</Text>
</View>
</View>
</NativeInfoViewGroup>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
},
content: {
width: '100%',
height: 120,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingTop: 10,
backgroundColor: 'white',
},
avatarImg: {
width: 100,
height: 100,
resizeMode: 'contain',
borderRadius: 50,
},
nameLayout: {
flex: 1,
flexDirection: 'column',
marginLeft: 16,
},
nameTxt: {
fontSize: 20,
color: '#333',
fontWeight: 'bold',
marginTop: 4,
},
descTxt: {
fontSize: 16,
color: '#666',
marginTop: 4,
},
})
原子组件实现原生组件
requireNativeComponent
- 与 原生通信传值 和 调用原生方法
- 继承是
SimpleViewManager
import React, { useRef, useEffect } from 'react'
import {
StyleSheet,
View,
requireNativeComponent,
ViewProps,
findNodeHandle,
UIManager,
} from 'react-native'
import { avatarUri } from '../constants/Uri'
type NativeInfoViewType =
| ViewProps
| {
avatar: string
name: string
desc: string
onShapeChange: (e: any) => void
}
const NativeInfoView =
requireNativeComponent<NativeInfoViewType>('NativeInfoView')
export default () => {
const ref = useRef(null)
useEffect(() => {
setTimeout(() => {
sendCommand('setShape', ['round'])
}, 3000)
}, [])
const sendCommand = (command: string, params: any[]) => {
const viewId = findNodeHandle(ref.current)
const commands = UIManager.NativeInfoView.Commands[command].toString()
UIManager.dispatchViewManagerCommand(viewId, commands, params)
}
return (
<NativeInfoView
ref={ref}
style={styles.infoView}
avatar={avatarUri}
name='尼古拉斯·段坤'
desc='各位产品经理大家好,我是个人开发者张三,我学习RN两年半了,我喜欢安卓、RN、Flutter,Thank you!。'
onShapeChange={(e: any) => {
console.log(e.nativeEvent.shape)
}}
/>
)
}
const styles = StyleSheet.create({
infoView: {
width: '100%',
height: '100%',
},
})
桥接原生容器组件
import React from 'react'
import { StyleSheet, requireNativeComponent, ViewProps } from 'react-native'
type NativeInfoViewGroupType =
| ViewProps
| {
}
const NativeInfoViewGroup = requireNativeComponent<NativeInfoViewGroupType>(
'NativeInfoViewGroup'
)
export default (props: any) => {
const { children } = props
return (
<NativeInfoViewGroup style={styles.infoView}>
{children}
</NativeInfoViewGroup>
)
}
const styles = StyleSheet.create({
infoView: {
width: '100%',
flexDirection: 'row',
},
})
React-Navigation 使用
npm install @react-navigation/native
npm install @react-navigation/stack
npm install @react-navigation/bottom-tabs
npm install react-native-gesture-handler
npm install react-native-screens
npm install react-native-safe-area-context
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
export default () => {
const Stack = createStackNavigator()
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name='Home' component={HomeScreen} />
<Stack.Screen name='Details' component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
- @react-navigation/bottom-tabs
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
const Tab = createBottomTabNavigator()
export default () => {
return (
<Tab.Navigator>
<Tab.Screen name='Home' component={HomeScreen} />
<Tab.Screen name='Settings' component={SettingsScreen} />
</Tab.Navigator>
)
}
import { StyleSheet, Text, View, Image, TouchableOpacity } from 'react-native'
import React from 'react'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { launchImageLibrary } from 'react-native-image-picker'
import Home from '../home/Home'
import Shop from '../shop/Shop'
import Mine from '../mine/Mine'
import Message from '../message/Message'
import icon_tab_publish from '../../assets/icon_tab_publish.png'
const BottomTab = createBottomTabNavigator()
export default () => {
const RedBookTabBar = ({ state, descriptors, navigation }: any) => {
const { routes, index } = state
return (
<View style={styles.tabBarContainer}>
{routes.map((route: any, i: number) => {
const { options } = descriptors[route.key]
const label = options.title
const isFoucsed = index === i
if (i === 2) {
return (
<TouchableOpacity
key={label}
style={styles.tabItem}
onPress={() => {
launchImageLibrary(
{
mediaType: 'photo',
includeBase64: false,
maxHeight: 300,
maxWidth: 300,
},
(response) => {
const { assets } = response
if (!assets || assets.length === 0) {
return
}
const { uri, width, height, fileName, fileSize, type } =
assets[0]
console.log('uri', uri)
console.log('width', width)
console.log('height', height)
console.log('fileName', fileName)
console.log('fileSize', fileSize)
console.log('type', type)
}
)
}}
>
<Image
style={styles.icon_tab_publish}
source={icon_tab_publish}
/>
</TouchableOpacity>
)
}
return (
<TouchableOpacity
key={label}
style={styles.tabItem}
onPress={() => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
})
if (!isFoucsed && !event.defaultPrevented) {
navigation.navigate(route.name)
}
}}
>
<Text
style={
isFoucsed
? styles.tabItemTextSelected
: styles.tabItemTextNormal
}
>
{label}
</Text>
</TouchableOpacity>
)
})}
</View>
)
}
return (
<View style={styles.root}>
<BottomTab.Navigator
tabBar={(props) => <RedBookTabBar {...props} />}
>
<BottomTab.Screen
name='Home'
component={Home}
options={{
title: '首页',
headerShown: false,
}}
/>
<BottomTab.Screen
name='Shop'
component={Shop}
options={{
title: '购物',
headerShown: false,
}}
/>
<BottomTab.Screen
name='Publish'
component={Shop}
options={{
title: '发布',
headerShown: false,
}}
/>
<BottomTab.Screen
name='Message'
component={Message}
options={{
title: '消息',
headerShown: false,
}}
/>
<BottomTab.Screen
name='Mine'
component={Mine}
options={{
title: '我',
headerShown: false,
}}
/>
</BottomTab.Navigator>
</View>
)
}
const styles = StyleSheet.create({
root: {
height: '100%',
width: '100%',
background: '#f5f5f5',
},
tabBarContainer: {
width: '100%',
height: 52,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
},
tabItem: {
flex: 1,
height: '100%',
justifyContent: 'center',
alignItems: 'center',
},
tabItemTextNormal: {
fontSize: 16,
color: '#999999',
},
tabItemTextSelected: {
fontSize: 18,
color: '#333',
fontWeight: 'bold',
},
icon_tab_publish: {
width: 52,
height: 52,
resizeMode: 'contain',
},
})
参考资料