4.零基础学RN-组件

1,120 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

1.简介

前面已经写了一个简单的App首页,现在我们开始再进行一些完善。主要包括:代码管理、搜索框功能、广告Banner、商品列表、下拉刷新、页面跳转等。

2.代码管理

相信大家都比较熟悉了,使用Git或Svn都可以,我把代码放在了gitee上(rn_tb链接),仅供参考。

创建工程的时候,rn已经创建好了.gitignore文件,所以在另一台设备下载完代码后,需要使用npm install来安装依赖。

2.1 重构代码

前面已经用到了StyleSheet,作用就是将控件的样式配置集中到一个位置,相当于css文件的作用,基本使用

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column'
  },
  input: {
    flex: 1,
    borderColor: 'gray',
    borderWidth: 2,
    borderRadius: 10
  }
)};

格式为key-value,在使用是使用styles.container即可,代码中重复的样式使用可以提取至StyleSheet中。

banner部分代码样式重构:

return (
    <View style={styles.ad_banner}>
        <ScrollView 
        horizontal={true}
        pagingEnabled={true}
        showsHorizontalScrollIndicator={false}
        ref = {com => this._banner = com}
        >
          <TouchableHighlight 
            onPress={() => {
              Alert.alert('点击banner', null, null);
            }}>
            <Image 
              style={{width:Dimensions.get('window').width, height: 180}}
              source={image_list.img1}/>
          </TouchableHighlight>
          <TouchableHighlight 
            onPress={() => {
              Alert.alert('点击banner', null, null);
            }}>
            <Image 
              style={{width:Dimensions.get('window').width, height: 180}}
              source={image_list.img2}/>
          </TouchableHighlight>
          <TouchableHighlight 
            onPress={() => {
              Alert.alert('点击banner', null, null);
            }}>
            <Image 
              style={{width:Dimensions.get('window').width, height: 180}}
              source={image_list.img3}/>
          </TouchableHighlight>
        </ScrollView>
      </View>
  );

在代码中修改:

const ScreenWidth = Dimensions.get('window').width;

// 样式
const styles = StyleSheet.create({
	...
  advContent: {
    width: ScreenWidth,
    height: 180
  }
  ...
});

return (
    <View style={styles.ad_banner}>
        <ScrollView 
        horizontal={true}
        pagingEnabled={true}
        showsHorizontalScrollIndicator={false}
        ref = {com => this._banner = com}
        >
          <TouchableHighlight 
            onPress={() => {
              Alert.alert('点击banner', null, null);
            }}>
            <Image 
              style={styles.advContent}
              source={image_list.img1}/>
          </TouchableHighlight>
          <TouchableHighlight 
            onPress={() => {
              Alert.alert('点击banner', null, null);
            }}>
            <Image 
              style={styles.advContent}
              source={image_list.img2}/>
          </TouchableHighlight>
          <TouchableHighlight 
            onPress={() => {
              Alert.alert('点击banner', null, null);
            }}>
            <Image 
              style={styles.advContent}
              source={image_list.img3}/>
          </TouchableHighlight>
        </ScrollView>
      </View>
  );

在banner中TouchableHighlight大部分代码还是重复的,只有很少的区别,可以使用js中的map()方法来进一步简化,类似java中的for in操作。

//定义广告数据
const Advertisements = [
  {title: '广告1', src: image_list.img1},
  {title: '广告2', src: image_list.img2},
  {title: '广告3', src: image_list.img3}
];

//修改代码
return (
    <View style={styles.ad_banner}>
        <ScrollView 
        horizontal={true}
        pagingEnabled={true}
        showsHorizontalScrollIndicator={false}
        ref = {com => this._banner = com}
        >
          {Advertisements.map((adv, index) => {
            return (
              <TouchableHighlight key={index} onPress={() => Alert.alert('点击banner:' + index, null, null)}>
                <Image style={styles.advContent} source={adv.src}/>
              </TouchableHighlight>
            );
          })}
        </ScrollView>
      </View>
  );

注:TouchableHighlight中的key={index}不能省略

代码少了很多,后面可以直接通过修改Advertisements的值来改变Banner。

3.搜索框功能-TextInput

点击搜索按钮后,可根据输入的内容进行搜索,首先要获取到输入的内容,这里需要使用到两个知识点

  • 使用TextInput中的onChangeText方法可以监听文本的变化
  • 使用useState可以将文本的内容保存下来

具体实现就是:

const SearchBar = () => {
  const [searchText, setSearchText] = useState('');

  return (
    <View style={styles.search_bar}>
      <TextInput style={styles.input} placeholder="搜索" onChangeText={(text) => {
        setSearchText(text);
      }}>{searchText}</TextInput>
      <Button onPress={() => {
        Alert.alert("输入内容:" + searchText, null, null);
      }} title="搜索"></Button>
    </View>
  );
}

效果:

4.banner指示器

一般广告控件都会增加一个指示器,用于标记当前显示的是第几个。我们可以偷懒一下,使用文本符号•来代表圆点,具体改动如下:

// 样式
const styles = StyleSheet.create({
  ...
  indicator: {
    width: ScreenWidth,
    height: 20,
    backgroundColor: 'rgba(0,0,0,0)',
    alignItems: 'center',
    justifyContent: 'center',
    top: 150,
    position: 'absolute',
    flexDirection: 'row'
  }
});

  return (
    <View style={styles.ad_banner}>
        <ScrollView 
        horizontal={true}
        pagingEnabled={true}
        showsHorizontalScrollIndicator={false}
        ref = {com => this._banner = com}
        >
          {Advertisements.map((adv, index) => {
            return (
              <TouchableHighlight key={index} onPress={() => Alert.alert('点击banner:' + index, null, null)}>
                <Image style={styles.advContent} source={adv.src}/>
              </TouchableHighlight>
            );
          })}
        </ScrollView>
        <View style={[styles.indicator]}>
          {Advertisements.map((_, index) => {
            return (
              <Text key={index} style={[(index === pageNumber) ? {color: 'orange'} : {color: 'white'}, {fontSize: 24}]}>
                 • 
              </Text>
            );
          })}
        </View>
      </View>
  );
};

效果:

5.商品列表-FlatList+Refresh

我们将商品列表改为Android中ListView的常用格式,左侧为icon,右侧为text,改动如下:

// 样式
const styles = StyleSheet.create({
  ...
  product_list: {
    flex: 1,
    justifyContent: 'center'
  },
  product_item: {
    padding: 10,
    fontSize: 18,
    height: 44,
    justifyContent: 'flex-start',
    flexDirection: 'row',
    alignItems: 'center'
  },
  product_img: {
    height: 40,
    width: 40,
    borderWidth: 0.5,
    borderColor: 'gray'
  }
  ...
});

const Products = () => {
  var data = [];
  for (var i = 0; i < 100; i++) {
    data.push({key: i, title: "商品" + i, desc: "这是描述信息"});
  }

  _renderImte = (item) => {
    return (
      <View style={styles.product_item}>
        <Image style={styles.product_img} source={require('./images/product_icon.png')}/>
        <View>
          <Text >{item.title}</Text>
          <Text >{item.desc}</Text>
        </View>
      </View>
    );
  };

  return (
    <View style={styles.product_list}>
      <FlatList 
      style={{flex: 1}}
        data={data}
        renderItem={({item}) => this._renderImte(item)}/>
    </View>
  );
}

将renderItem提取成一个函数,处理item的View,增加了左侧的图片引用,效果如下:

flatList中还提供了Header、Footer、SeparateLine等的实现,如下:

  _seperateLine = () => {
    return (
      <View style={{height: 1, backgroundColor: 'purple'}}></View>
    );
  };

  _itemHeader = () => {
    return (
      <Text style={{borderColor: 'red', borderWidth: 1}}>这是Header</Text>
    );
  };

  _itemFooter = () => {
    return (
      <Text style={{borderColor: 'yellow', borderWidth: 1}}>这是Footer</Text>
    );
  };

  return (
    <View style={styles.product_list}>
      <FlatList 
        style={{flex: 1}}
        data={data}
        renderItem={({item}) => this._renderImte(item)}
        ItemSeparatorComponent={this._seperateLine}
        ListHeaderComponent={this._itemHeader}
        ListFooterComponent={this._itemFooter}/>
    </View>
  );

效果:

增加下拉刷新,Flatlist中也提供了这个方法,真是太贴心了

const Products = () => {
  const [refreshing, setRefreshing] = useState(false)

  var data = [];
  for (var i = 0; i < 20; i++) {
    data.push({key: i, title: "商品" + i, desc: "这是描述信息"});
  }

  _renderImte = (item) => {
    return (
      <View style={styles.product_item}>
        <Image style={styles.product_img} source={require('./images/product_icon.png')}/>
        <View>
          <Text >{item.title}</Text>
          <Text >{item.desc}</Text>
        </View>
      </View>
    );
  };

  _seperateLine = () => {
    return (
      <View style={{height: 1, backgroundColor: 'purple'}}></View>
    );
  };

  _loadData = () => {
    setRefreshing(true);
    setTimeout(() => {
      Alert.alert("刷新完成", null, null);
      setRefreshing(false);
    }, 2000);
  };

  return (
    <View style={styles.product_list}>
      <FlatList 
        style={{flex: 1}}
        data={data}
        renderItem={({item}) => this._renderImte(item)}
        ItemSeparatorComponent={this._seperateLine}
        refreshing={refreshing}
        onRefresh={
          this._loadData
        }/>
    </View>
  );
}

效果:

使用onRefresh、refreshing两个方法即可轻松实现

6.页面跳转-ReactNavigation

页面跳转需要用到一个控件ReactNavigation,使用方法很简单:

第一步:安装控件及其依赖的控件

npm install @react-navigation/native
npm install react-native-screens react-native-safe-area-context
npm install @react-navigation/native-stack

第二步:定义路由文件,用于跳转,相当于Android中的ARouter,声明完之后就可以用对于的Key进行跳转

//router.js文件
const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Launch">
        <Stack.Screen name="Launch" component={Launcher} options={{headerShown: false}}/>
        <Stack.Screen name="Main" component={MainApp} options={{headerShown: false}}/>
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

第三步,跳转方法,在ReactNavigation声明过的控件中,props包含有navigation相关的值,可以打印一下日志查看,调用props.navigation.navigate就可以跳转界面啦,同样的还可以在第二个参数里传值

props.navigation.navigate('Main', {itemId: 1024, otherParm: 'fffffa'});

基于上面的说明,可以做一个这样的实现:增加一个launch界面,2s后跳转进入主界面;点击商品列表中的项,进入详情页面,实现代码如下:

//router.js
const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Launch">
        <Stack.Screen name="Launch" component={Launcher} options={{headerShown: false}}/>
        <Stack.Screen name="Main" component={MainApp} options={{headerShown: false}}/>
        <Stack.Screen name="Detail" component={DetailPage}/>
      </Stack.Navigator>
    </NavigationContainer>
  );
}
export default App;

//luanch.js
export const Launcher = (props) => {
    useEffect(() => {
        setTimeout(() => {
            props.navigation.replace('Main', {itemId: 1024, otherParm: 'fffffa'});
        }, 2000);
        return () => {
            
        }
    }, [])
    return (
        <Image style={{flex: 1, width: Dimensions.get('window').width, height: Dimensions.get('window').height}} source={require('./images/product_icon.png')}/>
    );
}

//main.js
  _renderImte = (item) => {
    return (
      <TouchableHighlight key={item.key} onPress={() => {
        props.navigation.navigate('Detail', {itemId: 1234, otherParams: item.title})
      }}>
        <View style={styles.product_item}>
          <Image style={styles.product_img} source={require('./images/product_icon.png')}/>
          <View>
            <Text >{item.title}</Text>
            <Text >{item.desc}</Text>
          </View>
        </View>
      </TouchableHighlight>
    );
  };

//detail.js
export function DetailPage({route, navigation}) {
    const name = route.params.otherParams

    return (
        <View>
            <Text>123123123</Text>
            <Text>{name}</Text>
        </View>
    );
}

目前为止,App的效果如下:

我们已经使用到了很多个常用的功能控件,其中部分只是一笔带过,比如下拉刷新等还有很多API可以使用,有空多查一下文档,多写写就熟悉了,重要的还是实战。

最近以我的菜鸟水平也开始被安排写一些项目中的RN代码了,有了收获会继续分享出来的。