React Native

    22. React-native实战系列

    Published
    April 21, 2023
    Reading Time
    6 min read
    Author
    Felix

    本文是关于 React-native 实战的系列内容,包括表单组件的封装(如 input、select、Toggling、DateTimePicker 等)和懒加载列表的实现。懒加载列表通过定义数据源、绘制组件图,运用相关 API 实现功能,并处理了搜索排序等逻辑。最后提到若有问题可通过[沸点]联系作者。

    关联问题: 如何优化懒加载效果 表单样式能自定义吗 安卓日期选择器难点

    AI智能总结首次生成速度较慢, 请耐心等待

    “ 本文正在参加「金石计划」 ”

    1、组件的使用

    移动端也有表单,表单也是我经常接触的一个场景,那么让我们就实现一些表单组件吧。

    1.1、封装一个input

    我们先从rnTextInput输入框开始

    input

    import React, { useState } from 'react'
    import PropTypes from 'prop-types'
    import { Text, TextInput, View } from 'react-native'
    import styles from './styles'
    function Input(props) {
      return (
        <View style={styles.textInputContainer}>
          <Text style={styles.textInputLabel}>{props.label}</Text>
          <TextInput style={styles.textInput} {...props} />
        </View>
      )
    }
    Input.propTypes = {
      label: PropTypes.string,
    }
    
    
    export default function CollectingTextInput() {
      const [changedText, setChangedText] = useState('')
      const [submittedText, setSubmittedText] = useState('')
      return (
        <View style={styles.container}>
          <Input label="Basic Text Input:" />
          <Input label="Password Input:" secureTextEntry />
          <Input label="Return Key:" returnKeyType="search" />
          <Input label="Placeholder Text:" placeholder="Search" />
          <Input
            label="Input Events:"
            onChangeText={e => {
              setChangedText(e)
            }}
            onSubmitEditing={e => {
              setSubmittedText(e.nativeEvent.text)
            }}
            onFocus={() => {
              setChangedText('')
              setSubmittedText('')
            }}
          />
          <Text>Changed: {changedText}</Text>
          <Text>Submitted: {submittedText}</Text>
        </View>
      )
    }
    
    
    

    让我们先看看在屏幕上这些输入框是这样的。

    image.png

    secureTextEntry密码输入盘,你的输入会被***挡住。

    Return key,这个值决定你的小键盘右下角的值。

    image.png

    我们也可以通过keyboardType去控制弹出的小键盘类型,比如numeric

    image.png

    1.2、封装一个select

    首先

    expo install @react-native-picker/picker
    
    

    然后我们可以用Picker实现一个选择器。 Select

    import { View, Text } from 'react-native'
    import { Picker } from '@react-native-picker/picker'
    import styles from './styles'
    export default function Select(props) {
      return (
        <View>
          <Text style={styles.pickerLabel}>{props.label}</Text>
          <Picker {...props}>
            {props.items.map(i => (
              <Picker.Item key={i.label} {...i} />
            ))}
          </Picker>
        </View>
      )
    }
    
    

    样式

    import { StyleSheet } from 'react-native'
    export default StyleSheet.create({
      container: {
        flex: 1,
        flexDirection: 'column',
        backgroundColor: 'ghostwhite',
        justifyContent: 'center',
      },
      pickersBlock: {
        flex: 2,
        flexDirection: 'row',
        justifyContent: 'space-around',
        alignItems: 'center',
      },
      pickerHeight: {
        height: 250,
      },
      pickerContainer: {
        flex: 1,
        flexDirection: 'column',
        alignItems: 'center',
        backgroundColor: 'white',
        padding: 6,
        height: 240,
      },
      pickerLabel: {
        fontSize: 14,
        fontWeight: 'bold',
      },
      picker: {
        width: 150,
        backgroundColor: 'white',
      },
      selection: {
        flex: 1,
        textAlign: 'center',
      },
    })
    
    
    

    app.js

    import React, { useState } from "react";
    import { View, Text } from "react-native";
    import styles from "./styles";
    import Select from "./Select";
    
    const sizes = [
      { label: "", value: null },
      { label: "S", value: "S" },
      { label: "M", value: "M" },
      { label: "L", value: "L" },
      { label: "XL", value: "XL" },
    ];
    
    const garments = [
      { label: "", value: null, sizes: ["S", "M", "L", "XL"] },
      { label: "Socks", value: 1, sizes: ["S", "L"] },
      { label: "Shirt", value: 2, sizes: ["M", "XL"] },
      { label: "Pants", value: 3, sizes: ["S", "L"] },
      { label: "Hat", value: 4, sizes: ["M", "XL"] },
    ];
    
    export default function SelectingOptions() {
      const [availableGarments, setAvailableGarments] = useState([]);
      const [selectedSize, setSelectedSize] = useState(null);
      const [selectedGarment, setSelectedGarment] = useState(null);
      const [selection, setSelection] = useState("");
    
      return (
        <View style={styles.container}>
          <View style={styles.pickersBlock}>
            <Select
              label="Size"
              items={sizes}
              selectedValue={selectedSize}
              onValueChange={(size) => {
                setSelectedSize(size);
                setSelectedGarment(null);
                setAvailableGarments(
                  garments.filter((i) => i.sizes.includes(size))
                );
              }}
            />
            <Select
              label="Garment"
              items={availableGarments}
              selectedValue={selectedGarment}
              onValueChange={(garment) => {
                setSelectedGarment(garment);
                setSelection(
                  `${selectedSize} ${
                    garments.find((i) => i.value === garment).label
                  }`
                );
              }}
            />
          </View>
          <Text style={styles.selection}>{selection}</Text>
        </View>
      );
    }
    
    

    最终效果如下:

    image.png

    1.3、封装一个Toggling

    import { View, Text, Switch } from 'react-native'
    import styles from './styles'
    export default function CustomSwitch(props) {
      return (
        <View style={styles.customSwitch}>
          <Text>{props.label}</Text>
          <Switch {...props} />
        </View>
      )
    }
    
    
    import React, { useState } from 'react'
    import { View } from 'react-native'
    import styles from './styles'
    import Switch from './Switch'
    export default function TogglingOnAndOff() {
      const [first, setFirst] = useState(false)
      const [second, setSecond] = useState(false)
      return (
        <View style={styles.container}>
          <Switch label="Disable Next Switch" value={first} disabled={second} onValueChange={setFirst} />
          <Switch label="Disable Previous Switch" value={second} disabled={first} onValueChange={setSecond} />
        </View>
      )
    }
    
    

    效果如下:

    image.png

    1.3、实现一个DateTimePicker

    首先下载依赖

    expo install @react-native-community/datetimepicker
    
    

    然后我们开发一个iosDateTimePicker组件

    import React from 'react'
    import { Text, View } from 'react-native'
    import DateTimePicker from '@react-native-community/datetimepicker'
    import styles from './styles'
    export default function DatePicker(props) {
      return (
        <View style={styles.datePickerContainer}>
          <Text style={styles.datePickerLabel}>{props.label}</Text>
          <DateTimePicker mode="date" display="spinner" {...props} />
        </View>
      )
    }
    
    
    

    ios上的表现

    image.png

    安卓会稍微麻烦一些

    import { Text, View } from 'react-native'
    import DateTimePicker from '@react-native-community/datetimepicker'
    import styles from './styles'
    function pickDate(options, onDateChange) {
      DateTimePicker.open(options).then(date => onDateChange(new Date(date.year, date.month, date.day)))
    }
    export default function DatePicker({ label, date, onDateChange }) {
      return (
        <View style={styles.datePickerContainer}>
          <Text style={styles.datePickerLabel}>{label}</Text>
          <Text onPress={() => pickDate({ date }, onDateChange)}>{date.toLocaleDateString()}</Text>
        </View>
      )
    }
    
    
    

    安卓上的表现

    image.png

    2、如何实现一个懒加载列表

    假设现在有一个需求需要我们实现一个懒加载的列表,它还可以过滤排序下拉刷新

    首先我们需要一个手动定义一个数据源,我们可以通过迭代器,实现一个无线调用递增的promise去模拟api的调用,当每次调用fetchItems的时候,我们的cnt都会增加30。

    function* genItems() {
      let cnt = 0
    
      while (true) {
        yield `Item ${cnt++}`
      }
    }
    
    const items = genItems()
    
    export function fetchItems() {
      return Promise.resolve({
        json: () =>
          Promise.resolve({
            items: new Array(30).fill(null).map(() => items.next().value),
          }),
      })
    }
    
    

    当我们定义了数据源后我们画一张整体的组件图吧。

    image.png

    我们先看一下我有哪些组件

    image.png

    这是我们最后想要的一个效果

    image.png

    ok,理论来说我们在写组件是应该跟随数据流,自上而下的写,但因为自下而上比较好让大家理解一点,我们就从里面往外面写。

    我们先从FlatList开始,大家应该对它很陌生,它是一个rn的内置组件,那么要实现我们的功能需要哪些api那。

    • 首先是懒加载我们需要使用onEndReached如果我们不指定距离,它会在滑动到内容最底部的距离为当前列表可见长度的一半时触发,接受一个函数。
    • 要实现下拉刷新,我们用onRefresh这个api我们需要传一个回调函数进去。
    • 而数据源是由data指定。
    • ListHeaderComponent就是一个表头。

    当我们已经知道需要哪些api了,就可以尝试写一下一个最基础的list组件

    List

    import PropTypes from 'prop-types'
    import { Text, FlatList } from 'react-native'
    import ListControls from './ListControls'
    import styles from './styles'
    
    export default function List({ data, fetchItems, refreshItems, isRefreshing, onFilter, onSort, asc }) {
      return (
        <FlatList
          data={data}
          renderItem={({ item }) => <Text style={styles.item}>{item.value}</Text>}
          ListHeaderComponent={<ListControls {...{ onFilter, onSort, asc }} />}
          onEndReached={fetchItems}
          onRefresh={refreshItems}
          refreshing={isRefreshing}
        />
      )
    }
    
    List.propTypes = {
      fetchItems: PropTypes.func.isRequired,
      refreshItems: PropTypes.func.isRequired,
      isRefreshing: PropTypes.bool.isRequired,
    }
    
    
    

    紧接着我们就需要去实现ListControlsListContainer

    ListControls

    import PropTypes from 'prop-types'
    import { View } from 'react-native'
    import styles from './styles'
    import ListFilter from './ListFilter'
    import ListSort from './ListSort'
    
    export default function ListControls({ onFilter, onSort, asc }) {
      return (
        <View style={styles.controls}>
          <ListFilter onFilter={onFilter} />
          <ListSort onSort={onSort} asc={asc} />
        </View>
      )
    }
    
    ListControls.propTypes = {
      onFilter: PropTypes.func.isRequired,
      onSort: PropTypes.func.isRequired,
      asc: PropTypes.bool.isRequired,
    }
    
    
    

    ListSort

    import PropTypes from 'prop-types'
    import { Text } from 'react-native'
    
    const arrows = new Map([
      [true, '▼'],
      [false, '▲'],
    ])
    
    export default function ListSort({ onSort, asc }) {
      return <Text onPress={onSort}>{arrows.get(asc)}</Text>
    }
    
    ListSort.propTypes = {
      onSort: PropTypes.func.isRequired,
      asc: PropTypes.bool.isRequired,
    }
    
    

    ListFilter,这里autoFocus是进来就自动聚焦调出小键盘。

    import PropTypes from 'prop-types'
    import { View, TextInput } from 'react-native'
    import styles from './styles'
    
    export default function ListFilter({ onFilter }) {
      return (
        <View>
          <TextInput autoFocus placeholder="Search" style={styles.filter} onChangeText={onFilter} />
        </View>
      )
    }
    
    ListFilter.propTypes = {
      onFilter: PropTypes.func.isRequired,
    }
    
    
    

    可以看到在这三个部分并没有太多的逻辑代码,都是一些用于展示的代码。我把所有的逻辑代码都放在来container中。

    container,此时想想我们需要在外层容器提供哪些状态。

    • 一个控制排序的状态asc
    • 一个控制loading的状态isRefreshing
    • 一个数据源pdata
    • 一个控制搜索过滤框的状态filter

    然后我们需要处理哪些逻辑

    • 搜索排序后的列表展示 fetchItem -> data -> filterAndSort(data) -> pdata=useMemo(data)
    • 每次往下拉的数据获取 fetchItems
    • 刷新后拉取新数据 refreshItems

    那么到这里我们就完成了一个懒加载搜索排序下拉刷新的列表

    import React, { useState, useEffect, useMemo } from 'react'
    import * as api from './api'
    import List from './List'
    
    const filterAndSort = (text, asc, array) => {
      if (array.length) {
        return array
          .filter(i => text.length === 0 || i.value.includes(text))
          .sort(asc ? (a, b) => a.index - b.index : (a, b) => b.index - a.index)
      }
    }
    
    const ListContainer = () => {
      const [data, setData] = useState([])
      const [isRefreshing, setIsRefreshing] = useState(false)
      const [asc, setAsc] = useState(true)
      const [filter, setFilter] = useState('')
      function fetchItems() {
        return api
          .fetchItems()
          .then(resp => resp.json())
          .then(({ items }) => {
            setData([...data, ...items])
          })
      }
    
      //我这的刷新模拟的是拉取新数据的过程
      function refreshItems() {
        setIsRefreshing(true)
        return api
          .fetchItems({ refresh: true })
          .then(resp => resp.json())
          .then(({ items }) => {
            setData(items)
          })
          .finally(() => {
            setIsRefreshing(false)
          })
      }
    
      useEffect(() => {
        fetchItems()
      }, [])
    
      const pdata = useMemo(() => {
        return filterAndSort(filter, asc, data)
      }, [filter, asc, data])
    
      return (
        <List
          data={pdata}
          fetchItems={fetchItems}
          refreshItems={refreshItems}
          isRefreshing={isRefreshing}
          asc={asc}
          onFilter={text => {
            setFilter(text)
          }}
          onSort={() => {
            setAsc(!asc)
          }}
        />
      )
    }
    
    export default ListContainer
    
    
    

    3、结束

    到这里我们已经了解了最基本的两种场景表单和列表,,em如果大家有问题,或者想学点更深入的东西直接看沸点加我就好,免费课就是为了让大家学懂跨端框架。