阅读 651

基于react-native-largelist-v3和react-native-spinkit的List封装

学习封装,减少没有必要的重复工作。

在封装前,先设计好这个组件应该具有的功能和api,然后在此基础上进行。

一、List组件的设计

应当尽可能的保持跟官网相似的api接口,减少学习成本。提供的api功能单一,不能一个api多种用途,减少入坑几率。应该有统一的规范,对组件的说明要明确,相同的行为只能有一种输出。

1.1 List规范与说明

此List既包含了FlatList也包含了SectionList。

  • 当传入的是data时是普通列表,传入的是sections时是分组列表
  • 上拉加载和下拉刷新使用的是react-native-spinkit,不可自定义
  • 必须指定每一项的高度,item的高度,section的高度可以不指定,不指定代表高度为0
  • 不能手动开启刷新和开始加载
  • 可以通过onRefresh中的回调结束刷新,也可以通过ref的方式结束刷新,加载相同

1.2 api定义

api类型说明api详情
dataarray数据源,一维数据
sectionsarray当是二维数组的数据
renderItem()=> React.ReactElement渲染的每一项
ListEmptyComponent()=> React.ReactElement当数据为空的时候渲染的组件
ListFooterComponent()=> React.ReactElement尾部组件
ListHeaderComponent()=> React.ReactElement头部组件
onRefreshfunction当刷新时调用的函数,提供一个回调,执行则代表刷新结束
onEndReachedfunction当到达底部调用的函数,提供一个回调,执行则代表上拉加载结束
itemHeight(index)=>number每一项的高度
sectionHeight(index)=>number每一组头部的高度
renderSection()=> React.ReactElement渲染的每一组的头部
loadingCompletedboolean当上拉加载的时候数据是否已经全部完成
styleViewStyle整体的样式
itemStyleViewStyle每一项的样式
sectionStyleViewStyle每一组头部的样式

提供的方法

api函数定义使用详情
scrollToEndlist.scrollToEnd();跳转到底部
scrollToStartlist.scrollToStart();跳转到顶部
scrollToIndexlist.scrollToIndex(index)跳转到指定item
scrollToSectionIndexlist.scrollToSectionIndex(index)跳转到指定section头部
endRefreshendRefresh()结束刷新
endEndReachedendEndReached()结束上拉加载

二、已经实现的api和未实现的api

  • data
  • sections
  • renderItem
  • ListEmptyComponent
  • ListFooterComponent
  • ListHeaderComponent
  • onRefresh
  • onEndReached
  • itemHeight
  • sectionHeight
  • renderSection
  • loadingCompleted
  • style
  • itemStyle
  • sectionStyle
  • scrollToEnd
  • scrollToStart
  • scrollToIndex
  • scrollToSectionIndex
  • endRefresh
  • endEndReached

三、具体的代码

import React, { forwardRef, useRef, useImperativeHandle } from 'react'
import { LargeList, RefreshHeader, SpinKit } from '../../../../modules'
import { View, StyleSheet } from 'react-native'
import { LoadingFooter } from 'react-native-spring-scrollview'


const __Refresh = ((refreshStyle) => {
  let refreshInstance = null
  return () => {
    if (refreshInstance === null) {
      refreshInstance = class extends RefreshHeader {
        render() {
          const { size = 20, type = "Circle", color = "red" } = refreshStyle || {}
          return (
            <View style={{ justifyContent: 'center', alignItems: 'center' }}>
              <SpinKit isVisible={true} size={size} type={type} color={color} />
            </View>
          )
        }
      }
    }
    return refreshInstance
  }
})()


const __EndReached = ((endReachedStyle) => {
  let endReachedInstance = null
  return () => {
    if (endReachedInstance === null) {
      endReachedInstance = class extends LoadingFooter {
        render() {
          const { size = 20, type = "Circle", color = "red" } = endReachedStyle || {}
          return (
            <View style={{ justifyContent: 'center', alignItems: 'center' }}>
              <SpinKit isVisible={true} size={size} type={type} color={color} />
            </View>
          )
        }
      }
    }
    return endReachedInstance
  }
})()

const getData = (data, sections) => {
  if (!data) data = []
  else if (!sections) sections = []

  if (!Array.isArray(data)) throw new Error("你传入的并不是数组,而是:", typeof data)

  if (data) {
    return [{ items: data }]
  } else {
    return sections || []
  }
}
const RenderItem = (section, index, data, renderItem, itemStyle, itemHeight) => {
  return (<View style={StyleSheet.flatten([itemStyle, { height: itemHeight({ section, index }), overflow: 'hidden' }])}>
    {renderItem({ item: data[section].items[index], index })}
  </View>)
}
const RenderSection = (section, data, renderSection, sectionStyle, sectionHeight) => {
  return (<View style={StyleSheet.flatten([sectionStyle, { height: sectionHeight(section), overflow: 'hidden' }])}>
    {renderSection({ section: data[section], index: section })}
  </View>)
}
const List = forwardRef(({
  data,
  renderItem,
  ListFooterComponent,
  ListHeaderComponent,
  onRefresh,
  onEndReached,
  itemHeight,
  sectionHeight,
  renderSection,
  loadingCompleted,
  style = {},
  refreshStyle = {},
  endReachedStyle = {}
}, ref) => {
  const listRef = useRef(null)
  useImperativeHandle(ref, () => {
    return {
      ...listRef.current || {},
      scrollToStart: () => {
        console.log("靠转到scrollToStart")
        listRef.current.scrollToIndexPath({ section: 0, row: 0 })
      },
      scrollToEnd: () => {
        let row = data[data.length - 1].items.length - 1
        listRef.current.scrollToIndexPath({ section: data.length - 1, row })
      },
      scrollToIndex: (index) => {
        if (data.length > 1) {
          console.warn("此api适用于普通列表,分组列表请使用scrollToSectionIndex")
        }
        if (index >= data[0].length) {
          index = data[0].length - 1
        }
        listRef.current.scrollToIndexPath({ section: 0, row: index })
      },
      scrollToSectionIndex: (section) => {
        if (section >= data.length) {
          section = data.length - 1
        }
        listRef.current.scrollToIndexPath({ section: section, row: 0 })
      }
    }
  })

  const endRefresh = () => {
    if (listRef && listRef.current) {
      listRef.current.endRefresh()
    }
  }
  const _onRefresh = () => {
    onRefresh && onRefresh(endRefresh)
  }
  const endLoading = () => {
    if (listRef && listRef.current) {
      listRef.current.endLoading
    }
  }
  const _onEndReached = () => {
    onEndReached && onEndReached(endLoading)
  }

  if (!itemHeight) {
    throw new Error("必须指定每一项的高度")
  } else if (typeof itemHeight !== 'function') {
    throw new Error("itemHeight需要的类型是函数,并不是:", typeof (itemHeight))
  }
  return (
    <LargeList
      data={data}
      ref={listRef}
      style={style}
      {...onRefresh && { refreshHeader: __Refresh(refreshStyle), onRefresh: _onRefresh }}
      {...onEndReached && { loadingFooter: __EndReached(endReachedStyle), onEndReached: _onEndReached }}
      heightForIndexPath={itemHeight}
      renderHeader={ListHeaderComponent}
      renderFooter={ListFooterComponent}
      renderSection={renderSection}
      allLoaded={loadingCompleted}
      heightForSection={sectionHeight}
      renderIndexPath={renderItem} />
  )
})


const SwitchEmptyOrList = forwardRef(({
  data, // 数据,一维数组
  sections,
  renderItem,
  itemStyle,
  renderSection,
  sectionStyle,
  sectionHeight = () => 0,
  itemHeight,
  ListEmptyComponent = () => null,
  ...props
}, ref) => {
  const _data = getData(data, sections)
  if (_data.length === 0) {
    return ListEmptyComponent()
  } else {
    return (<List
      data={_data}
      ref={ref}
      renderItem={({ section, row }) => RenderItem(section, row, _data, renderItem, itemStyle, itemHeight)}
      {..._data.length > 1 && {
        renderSection,
        sectionHeight: (section) => RenderSection(section, _data, renderSection, sectionStyle, sectionHeight)
      }}
      itemHeight={itemHeight}
      {...props} />)
  }
})


export default SwitchEmptyOrList
复制代码

四、简单使用

function TestList() {
  const _renderItem = ({item, index}) => {
    return (
       <Text key={index}>{item}</Text>
    )
  }
  return (
    <List 
       data={[1, 2, 3, 4, 5, 6]}
       itemHeight={() => 50}
       renderItem={_renderItem}
     />
   )
 }
复制代码
文章分类
前端
文章标签