react使用高阶组件的方式封装一个remoteInput组件

1.什么是高阶组件?

参照高阶函数的定义:高阶函数是一个接受一个函数作为参数的,可以操作其他函数的函数,我们可以把高阶组件做这样一个定义:高阶组件是一个接受一个组件作为参数的,可以操作其他其他组件的组件。

2.如何实现一个高阶组件?

我们往往把高阶组件定义为一个函数组件,因为通过使用函数我们可以很方便接受一个组件作为原始组件,然后返回一个新的组件,在这个新组件中挂载渲染原始组件,同时传入所有的props和高阶组件中的state或者方法。此时,高阶组件作为原始组件的装饰器,在不修改原始组件的前提下添加了一些新的功能,给原始组件做了增强,这种模式往往叫做装饰器模式。

3.使用装饰器模式封装一个remoteInput组件

首先明确两个定义,木偶组件和智能组件,木偶组件是纯ui展示的组件没有任何逻辑,父组件传给它什么它就用什么,就像个提线木偶一样,智能组件负责数据的维护和逻辑的处理,往往会把一个行为相关的数据和方法封装到一个智能组件里,智能组件再把这些数据和方法通过props传入到下一个组件中,最后都传到木偶组件中,智能组件就像是操纵木偶的手。

我们会封装一下几个木偶组件:
高阶组件
FlodAble负责实现列表展开和收起的动画:
它的实现比较简单,通过改变transform的值来实现展开和收起。具体实现如下:

function FlodAble(props){
    const {flod,children}=props
    return (
        <div
            style={{transform:flod?'translate3d(0, 0, 0) scaleY(0)':'translate3d(0, 0, 0) scaleY(1)'}}
            className={'flodAbleArea'}
        >
            {children}
        </div>
    )
}
export default FlodAble

使用translate3d(0, 0, 0)属性可以开启gpu加速,动画会更流畅。
flodAbleArea的样式为:

.flodAbleArea{
    transition: all 0.3s;
    transform-origin: 50% 0;
    border-radius: 5px;
    box-shadow:0 0 5px 1px grey;
    padding: 10px;
    position: absolute;
    z-index: 99999 !important;
    width: 100%;
    background: #ffffff;
}

List组件负责列表呈现,主要分为两部分:
Item,接受text和onItemClick回调函数,负责呈现条目:

function Item (props) {
    const {text,onItemClick}=props
    return (
       <li className={'list-item'} onClick={()=>{onItemClick(text)}}>{text}</li>
    )

}

List:接受数据集合和管理Item:

function List(props){
    const {dataList,onItemClick}=props
    return (
          <ul className={'list'}>
            {
              dataList.map((d,i)=>
                  <Item key={i} text={d} onItemClick={onItemClick}/>
              )
            }
          </ul>
    )
}

SearchInput负责响应用户的输入和调用接口:
Search直接使用antd的组件。

function SearchInput(props){
    const {value,onChange,placeholder,onSearch}=props
    return (
      <Search value={value}
             onChange={onChange}
             placeholder={placeholder}
             onSearch={onSearch}
      />
    )
}

Selector用于管理木偶组件和分发props到相应的木偶组件:
由于Component已经是组件的实例且我们需要传递props所以使用React.cloneElement克隆实例的时候传入props。

function Selector(props){
    let {value,onChange,placeholder, dataList,onItemClick,loading,width,flod,onSearch}=props
    return (
      <div style={{width}}>
        {
              props.children.map((Component,i)=>{
              if(Component.type.name==='SearchInput'){
                return React.cloneElement(Component,{
                      value,
                      onChange,
                      placeholder,
                      onSearch,
                      key:'SearchInput'
                    })
              }
              if(Component.type.name==='List'){
                return <div className="ListWrap" key={'ListWrap'}>
                      <FlodAble flod={flod} >
                       <Spin spinning={loading} >
                        {
                            (function () {
                                if(dataList.length===0){
                                    return  loading?'拼命加载中。。。。。':'没有数据'
                                }else{
                                    return  React.cloneElement(Component,{
                                        loading,
                                        dataList,
                                        onItemClick,
                                        key:'List'
                                    })
                                }
                            })()
                        }
                      </Spin>
                    </FlodAble>
                   </div>
              }
          }
          )
        }
      </div>
    )
}

接下来,我们根据业务封装几个智能组件:
智能组件

AsnycDataDecorator用于执行获取数据的回调:

function AsnycDataDecorator(WrappedComponent){
    function AsnycDataDecorator (props) {
        const [dataList,setDataList]=useState([])
        const [loading,setLoading]=useState(false)


     function getData(value){
         setLoading(true)
          props.unfold()
         new Promise((resolve,reject)=>{
             props.fetchData(value,resolve,reject)
         }).then((value)=>{setDataList(value);setLoading(false)})
             .catch(((reason)=>{setLoading(false);setDataList([reason])}))
     }

     function clearData() {
         setDataList([])
     }
       return (
          <WrappedComponent
              { ...props}
              loading={loading}
              dataList={dataList}
              getData={getData}
              clearData={clearData}
          />
       )
   }
  return AsnycDataDecorator
}

在获取数据的时候设置loading并且展开列表,请求完成后关闭loading并且把数据放到state中,
这个组件把loading、dataList、getData、clearData加入到数据流中。

FlodDecorator负责控制FlodAble的展开与收起:

function FlodDecorator(WrappedComponent){
    function FlodDecorator(props) {
     const [flod,setFlod]=useState(true)
        useEffect(()=>{
            window.addEventListener('click',()=>{
                setFlod(true)
            })
            return function () {
                window.removeEventListener('click')
            }
        },[])

        function unfold(){
            setFlod(false)
        }

       return (
          <WrappedComponent
              { ...props}
              flod={flod}
              unfold={unfold}
          />
       )

   }
  return FlodDecorator
}

我们在副作用函数中在window添加了一个事件监听,当用户点击页面时收起列表。
这个组件把flod、unfold加入到数据流中。

SearchInputDecorator负责控制input中的数据显示:

function SearchInputDecorator(WrappedComponent){
    function SearchInputDecorator(props){

     const [value,setValue]=useState('')
     function onChange(e){
        let value=e.target.value
         setValue(value)
         props.getData(value)
     }
     function onItemClick(value){
         setValue(value)
         props.clearData&&props.clearData()
     }

       return (
          <WrappedComponent
              { ...props}
              onChange={onChange}
              onItemClick={onItemClick}
              value={value}
          />
       )
   }
  return SearchInputDecorator
}

在用户输入内容的时更新state并调用请求数据的回调函数,
在用户点击列表中的条目时更新state并且清除掉原理列表中的数据。
这个组件把onChange、onItemClick、value加入到数据流中。

组合木偶组件和智能组件:

const  FinalComponent=compose(FlodDecorator,AsnycDataDecorator,SearchInputDecorator)(Selector)
function RemoteInput(props){
    return (
      <FinalComponent {...props}>
       <SearchInput/>
       <List/>
      </FinalComponent>
    )
}

我们使用compose函数把这些组件组合起来,关于compose可以看这里:简单实现compose
需要注意的是确保compose只会调用一次,如果写在了render等其他会多次调用场景时会实例化多次从而导致丢失实例,组件内的状态会丢失,而且会带来性能上的问题。

使用组件:

<RemoteInput
                placeholder={'输入城市搜索'}
                onSearch={(value) => { console.log(`onSearch: ${value}`) }}
                fetchData={fetchData}
                width={'500px'}
            />

我们在fetchData中使用timeout模拟向后台请求数据并做了一个简单的防抖:

function fetchData(value,resolve,reject){
          let dataList=[]

       clearTimeout(timeout)
          timeout=setTimeout(()=>{

                list.forEach((d)=>{
                if(value.length>0&&d.indexOf(value)!==-1){
                    dataList.push(d)
                }
            })
            resolve(dataList)
        },1000)
    }

到现在组件的封住就完成了。

4.总结

咋看一个这么简单组件搞出了这么多子组件出来,而且还一层套一层,头都大了,不如一把梭全写在一个组件里,省事直观方便。其实高阶组件的优点就如它的缺点一样直观:
1.增加代码的复用率,组件中的木偶组件和智能组件都可以复用到其他组件之中,一处修改四处生效。
2.增强了组件的可维护性,当要加入新的功能的时候只要添加新的智能组件就好了,不必对原来的组件 进行修改,如果一个组件被多个人多次修改之后会变得更加难以维护。
3.复杂组件可以根据用户的配置按需加载相应的功能和逻辑,避免一些用户不需要的代码加载进来。
最后放一下代码的地址:代码地址

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值