当前位置: 首页 > Web前端 > HTML

React开发实战(一)——重复组件

时间:2023-04-02 18:29:15 HTML

前言最近在写一个React初学者玩转React的系列教程。对于有React开发经验的同学来说,内容可能过于基础和啰嗦,兴趣不大。所以我打算同时开始另一系列的文章《React 开发实战》。本系列主要面向有React开发经验的同学,更侧重于React实战。每篇文章都会和你一起开发一个React组件或者一个简单有趣的React应用。这些组件或应用程序往往符合以下特点:在我的实际项目中使用。在常见的开源组件库中不可用。有点小众,但是在特定的业务场景下可以极大的提升项目的开发效率。可能更有趣。如果这些组件能直接应用到你的实际开发中就好了;如果没有,我认为给你一些启发是非常有价值的。此外,每篇文章后面都附有本文的完整示例和代码。问题描述大家应该都见过这种应用场景。页面的某个部分需要允许用户添加任意??数量的项目。可以是如下形式的字段。也可以是表单的一部分,如下图,用户可以在一个表单中添加多个用户信息,然后批量保存用户信息。还有更变态的,如下图,一个表单可以添加多份用户信息,每个用户信息中的地址也可以添加多份。(Oh,MyGod.PM,youkillme.)幸运的是,React可以处理这种需求,还是小菜一碟。但是如果一个web应用中有那么多类似的场景,如果我们一个一个去实现,那就真的很无聊了,和搬砖没什么两样。这种情况下,我们需要将相同的功能抽象出来,做成组件,这样会大大提高你的开发效率。基于这种场景,今天我们要开发一个组件,可以让它的子组件重复任意数量的副本,姑且称之为Repeat。您希望如何使用Repeat组件?在开发一个组件的时候,不要急着写代码,先想想你要做这个组件是什么,比如这个Repeat组件,我希望有如下特点:Repeat组件提供默认的,add,Remove按钮。单击“添加”以复制React的子项,然后单击“删除”以删除一个项目。只有一项时无法删除。Repeat支持onChange回调函数,当Repeat中的表单输入发生变化时,可以立即通知其父组件。然后在代码中我希望像这样使用Repeat组件:classAppextendsReact.Component{handleChange(items){console.info(items);}render(){this.handleChange(items)}>}}OK,就这么简单,这样Input组件就可以添加多个反复复印。基于这个思路,我们来实现Repeat组件。开始实现Repeat组类RepeatextendsReact.Component{constructor(props){super(props);this.state={items:[''],};}handleChange(e,index){constitems=[...this.state.items];items[index]=e.target.value;this.setState({items});this.props.onChange(items);}handleAddItem(e,index){e.preventDefault();constitems=[...this.state.items];items.splice(index,0,'');this.setState({items});}handleRemoveItem(e,index){e.preventDefault();如果(this.state.items.length===1)返回;constitems=[...this.state.items];items.splice(index,1);this.setState({items});}render(){constchildren=React.Children.only(this.props.children);constelementItems=this.state.items.map((item,index)=>({React.cloneElement(children,{onChange:e=>this.handleChange(e,index),value:item,})}

this.handleAddItem(e,index)}>添加this.handleRemoveItem(e,index)}>删除
));返回
{elementItems}
;}}代码很简单,我简单说明一下:组件的state持有items字段,用来保存每个item的数据。渲染时,首先获取唯一的子项,然后映射组件状态项,将每个项映射到子项的副本。并为这个copy传入两个属性,onChange接收每个item的数据变化,value传递每个item当前应该显示的值。另外,Repeat为每个item都准备了一个“Add”按钮和一个“Remove”按钮,用于在当前item位置添加一个item或者移除当前item。原理是删除this.state.items中对应下标处的数组元素。至此,Repeat大致成型。需要提醒的是,React.cloneElement和React.Children.xxx等API通常只在这类公共组件中使用,在大多数场景下应谨慎使用。和children有约定有些同学可能已经发现了,在上面的例子中,Repeat的children是一个input,所以如果是另外一个组件,就结束了。这是第一个问题。为了解决这个问题,Repeat需要为其子级提供两个条件:属性必须接收一个onChange回调函数,该函数接收一个对象参数。参数结构如下:{target:{value:'xxxx'的值}}value是当前item的输出数据,可以是对象也可以是字符串也可以是值。没错,就是想兼容inputevent的数据结构。您当然可以使用您喜欢的任何方便的数据结构。子组件需要接收一个valueprop来显示它的值。换句话说,子组件应该是受控组件。这是一个协议。如果你想在某个组件中通过Repeat组件方便地添加多个副本并获取一组数据,就必须遵守这个协议。有的同学可能会说为什么不聪明点呢?好吧,在这里我想分享一点个人经验:有时候,尤其是在业务开发过程中,将public部分抽取出来重用就可以了。没必要搞得这么“厉害”。遵循协议很容易完成,实际上会更高效,更容易让人理解。事实上,计算机世界充满了协议。比如,你想让HTTPServer返回一个正确的响应,就必须遵循http协议与之通信;你生产的显卡是可以买到的,必须遵守相应的协议。可以插在别人生产的主板上。离这很远!收到!是的,有了上面的约定,Repeat的一行代码没有加,是不是感觉功能提升了很多?嗯,这就是目的。下面我们来实现文章开头提到的第二种场景。聪明的你一定已经知道怎么做了,是的,只要我们实现一个UserForm组件,让它满足上面的约定即可。请看代码:classUserFormextendsReact.Component{handleFieldChange(e){const{name,value}=e.target;constformData={...this.props.value,[name]:value,}this.props.onChange({target:{value:formData,}});}render(){constformData=this.props.value||{};return(
名字this.handleFieldChange(e)}/>
地址this.handleFieldChange(e)}/>
)}}为了让代码更简洁,我实现了UserForm组件作为支撑controlledComponents,但是在现在的业务场景下已经足够了。在实际情况中,您可以根据自己的需要进行调整。通过这个例子,希望大家能够体会到组件拆分的一个好处。即UserForm和Repeat拆分成两个组件后,UserForm的复用性会更高。可以想象,批量添加用户后,是否可以在编辑单个用户时继续使用该组件。那么对于第三种情况,我觉得没有必要再去实施了。实际上可以嵌套尽可能多的Repeat层。更进一步实际上,在实际应用中,Repeat组件还需要进一步完善,其中之一就是样式,而且有可能在不同的场景下,虽然交互相同,但样式会有所不同。另外,默认有“Add”和“Remove”两个文本按钮。实际业务场景中可能会有+、-两个图标按钮;“添加”和“删除”的位置可能会发生变化。如何处理这些问题?我给大家描述一下思路,具体的代码我就不写了。如果您有任何问题,可以给我留言。关于样式,可以在Repeat中添加itemClassName和buttonsClassName两个属性,分别作为每个item和按钮区域的cssclass。这样就可以在不同的场景下指定不同的样式。关于如何将文本按钮变为图标按钮,可以在Repeat中添加renderButtons等函数属性。如果不指定,按钮将以默认方式渲染,如果有,则勇于返回渲染属性的值。最后,这是本文的代码:https://codepen.io/Sarike/pen...好了,文章到此结束,有什么问题可以给我留言。谢谢大家,祝大家国庆中秋快乐。