2020年06月24日胖蔡前端ListView,React,前端,自定义组件,适配器24次阅读

开始定义组件前,我首先先整理了一下我的需求,然后让我可以按照需求去定义实现组件功能。我此次定义的是一个移动端的List组件,参照Android端ListView的功能特性,我梳理了下我需要实现的几个功能点:

  • 适配器实现
  • 增量添加
  • 数据更新
  • 局部更新
  • Header和Footer
  • 分割线(Divider)
  • 子view复用

本文主要实现ListView的适配器功能,基本可以解决列表的绝大部分问题。

ListView组件实现

通过适配器的实现,适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。这样就可以让ListView如同Android中的ListView一样动态设置Item布局、数据格式,Item类型等。

/* eslint no-dupe-keys: 0 */ import React, { Component } from "react"; class ListView extends Component{ constructor(props) { super(props); // console.log("get props:",this.props) this.state = { data: props.data?props.data:[], }; } componentDidMount() { //组件首次加载时 console.log("get componentDidMount props:",this.props) }; componentWillReceiveProps(nextProps){ if(this.props.data != nextProps.data){ this.setState({ data:nextProps.data?nextProps.data:[], }) } }; /** * * @param {*子布局数据格式} item * @param {*子布局位置} index */ getItem(item,index){ let itemLayout = this.props.adapter(item,index,{onClick:this.onItemClick.bind(this,item,index)}) return itemLayout }; onItemClick(item,index){ console.log("-------------pre click log--------------") return this.props.OnItemClick(item,index) }; render() { return ( <div className="list" > { { !!this.state.data?(this.state.data.map((item,index) => { return this.getItem(item,index) })):null } } </div> ); } } export default ListView;

ListView组件调用

ListView可以在任意页面调用使用,只需在listView中指定adapter适配内容和点击操作接口即可,代码调用如下:

import React, { Component } from "react"; import { connect } from "react-redux"; import { bindActionCreators } from "redux"; import { NavBar, Icon, Carousel, WingBlank, NoticeBar, WhiteSpace, } from "antd-mobile"; import { StickyContainer, Sticky } from "react-sticky"; import ListView from "./listview"; import * as homeActions from "../redux/reduces/home"; import { Toast } from "../../node_modules/antd-mobile/lib/index"; import axios from "axios"; axios.defaults.timeout = 100000; axios.defaults.baseURL = "http://test.mediastack.cn/"; /** * http request 拦截器 */ axios.interceptors.request.use( (config) => { config.data = JSON.stringify(config.data); config.headers = { "Content-Type": "application/json", }; return config; }, (error) => { return Promise.reject(error); } ); @connect( (state) => ({ home: state.home }), (dispatch) => bindActionCreators(homeActions, dispatch) ) class Home extends Component { state = { data: ["1", "2", "3"], imgHeight: 176, }; constructor(props) { super(props); setTimeout(() => { this.setState({ data: [ "AiyWuByWklrrUDlFignR", "TekJlZRVCjLFexlOCuWn", "IJOtIlfsYdTyaDTRVrLI", ], }); }, 100); } componentDidMount() { axios.get('/article/home/index').then( (res) => { this.setState({ list: res.data.data, }); console.log("get article response:", res); }, (error) => { console.log("get response failed:", error); } ); } handleBrowserChange = () => { const { history, changeRoute } = this.props; changeRoute(); history.push("/docs"); }; /** * 推荐内容适配器 * @param {*} item * @param {*} index */ RecommentAdapter(item, index,props) { return ( <div {...props} className="item" style={{ height: 80, backgroundColor: "#fff", textAlign: "left", padding: "0 15px 0", display: "flex", fontSize: 20, flexFlow: "column nowrap", justifyContent: "center", marginBottom: 10, }} key={index} > <label className="title" style={{ minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", }} > {item.title} </label> <div style={{ fontSize:12, marginTop:12, color:'#888888' }}> <span style={{ color:'#111111', fontWeight:'500', }}>{item.category}</span> | <span> {item.author}</span> | <span> 阅读量:{item.views}</span> | <span> {item.date}</span> </div> </div> ); }; onItemClick(item,index){ Toast.show("你点击的第"+index+"个item.",Toast.SHORT) }; render() { return ( <div className="home"> <StickyContainer> <Sticky> {({ style, isSticky, wasSticky, distanceFromTop, distanceFromBottom, calculatedHeight, }) => ( <NavBar style={{ ...style, zIndex: 3, padding: "2px 0", }} mode="light" icon={<Icon type="left" />} onLeftClick={() => console.log("onLeftClick")} rightContent={[ <Icon key="0" type="search" style={{ marginRight: "16px" }} />, <Icon key="1" type="ellipsis" />, ]} > 胖蔡杂谈 </NavBar> )} </Sticky> {/* Sticky 为悬浮框 */} <WingBlank> <WhiteSpace size="lg" /> <Carousel autoplay={false} infinite beforeChange={(from, to) => console.log(`slide from ${from} to ${to}`) } afterChange={(index) => console.log("slide to", index)} > {this.state.data.map((val) => ( <a key={val} href="http://www.alipay.com" style={{ display: "inline-block", width: "100%", height: this.state.imgHeight, }} > <img src={`https://zos.alipayobjects.com/rmsportal/${val}.png`} alt="" style={{ width: "100%", verticalAlign: "top", }} onLoad={() => { // fire window resize event to change height window.dispatchEvent(new Event("resize")); this.setState({ imgHeight: "auto", }); }} /> </a> ))} </Carousel> <WhiteSpace size="lg" /> <NoticeBar marqueeProps={{ loop: true, style: { padding: "0 7.5px" } }} > 通知:北京继续暂停出租车顺风车出京运营 合肥明日继续新一波消费券发放,预计发放4000万元 </NoticeBar> <WhiteSpace size="lg" /> <ListView data={this.state.list} adapter={this.RecommentAdapter} OnItemClick={this.onItemClick}/> </WingBlank> </StickyContainer> </div> ); } } export default Home;

adapter 返回单个Item的jsx,可通过React组件或者方法返回均可。