React 开发实战(一)- Repeat 组件

前言

最近在写一个面向 React 初学者的系列教程玩转 React,内容对有 React 开发经验的同学来说可能太过于基础和啰嗦,不太感兴趣。所以我打算同时开始另外一个系列文章《React 开发实战》。该系列主要面向有 React 开发经验的同学,更侧重 React 实战,每一篇文章会跟大家一起开发一个 React 组件或者一个简单有趣的 React 应用,这些组件或者应用往往满足如下特点:

  • 在我的实际项目中用到过的。
  • 在常见的开源组件库中没有的。
  • 有点小众,但是在特定的业务场景下能很大地提高项目的开发效率。
  • 可能还比较有趣。

如果这些组件能直接应用到大家的实际开发中去,那再好不过了;如果不能,能给大家一点启发,我觉得这件事情也是很有价值的。

另外,每一篇文章后面都会附有本篇文章的完整示例和代码。

问题描述

大家应该都见过这种应用场景,页面上的某一部分,需要能够让用户添加任意多项。

可能是表单中的一个字段,如下所示。

也可能是表单的一部分,如下所示,用户可以在一个表单内增加多个用户信息,然后将用户信息批量进行保存。

还有更变态的,如下所示,一个表单内用户信息部分可以添加多份,每一个用户信息中地址也可以添加多份。(Oh, My God. PM,你杀了我吧。)

还好,React 应付这种需求,还是小菜一碟。但是在一个 web 应用中有这么多的相似场景的话,如果我们挨个实现一遍,那真是太枯燥了,与搬砖无异。遇到这种情况,就需要我们把相同的功能抽象出来,做成组件,这将极大地提升你的开发效率。

基于这个场景,我们今天就开发一个能让其 children 重复任意多份的组件,我们就称之为 Repeat 吧。

你期望 Repeat 组件该怎么用

在开发一个组件的时候,不要着急写代码,先想想你要把这个组件做成什么样子,例如这个 Repeat 组件,我希望有如下特性:

  • Repeat 组件提供默认的,添加、移除按钮。
  • 点击添加,将 React 的 children 复制一份,点击移除将某一项移除。
  • 当只有一项时不能移除。
  • Repeat 支持 onChange 回调函数,当 Repeat 内的表单输入发生变化时可以即时通知其父组件。

然后在代码中我期望可以这样来用 Repeat 这个组件:

class App extends React.Component {
    handleChange(items) {
        console.info(items);
    }
    render() {
        <Repeat onChange={items => this.handleChange(items)}>
            <input  type="text" />
        </Repeat>
    }
}

OK,就是这么简单,这样 Input 组件就可以重复加添多份了。基于这个构想,我们来实现 Repeat 这个组件。

开始实现 Repeat 组件

class Repeat extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            items: [‘‘],
        };
    }
    handleChange(e, index) {
        const items = [...this.state.items];
        items[index] = e.target.value;
        this.setState({ items });
        this.props.onChange(items);
    }
    handleAddItem(e, index) {
        e.preventDefault();
        const items = [...this.state.items];
        items.splice(index, 0, ‘‘);
        this.setState({ items });
    }
    handleRemoveItem(e, index) {
        e.preventDefault();
        if (this.state.items.length === 1) return;
        const items = [...this.state.items];
        items.splice(index, 1);
        this.setState({ items });
    }
    render() {
        const children = React.Children.only(this.props.children);
        const elementItems = this.state.items.map((item, index) => (
            <div key={index}>
                {
                    React.cloneElement(children, {
                        onChange: e => this.handleChange(e, index),
                        value: item,
                    })
                }
                <div>
                    <a href="#" onClick={e => this.handleAddItem(e, index)}>添加</a>
                    <a href="#" onClick={e => this.handleRemoveItem(e, index)}>移除</a>
                </div>
            </div>
        ));
        return <div>{elementItems}</div>;
    }
}

代码很简单,简单解释一下:

  • 组件的 state 中持有 items 字段来保存每一个项的数据。
  • render 时先获取到唯一的 children,然后 map 组件 state 中的 items,将每一项映射为 children 的一个副本。并为这个副本传入两个属性,onChange 接收每一项的数据变化,value 传递每一项当前应展示的值。
  • 另外 Repeat 为每一项准备了一个“添加”按钮和一个“移除”按钮,用来在当前项位置新增一项或者移除当前项。原理就是将 this.state.items 中对应下标处的数组元素删掉就好了。

到此,Repeat 是不是大致有模有样了呢。需要提醒大家的是,React.cloneElementReact.Children.xxx 这些 api 通常只会在这种公共组件中使用,在大部分场景,尽量少用。

跟 children 有个约定

有些同学可能已经发现了,上面例子中, Repeatchildren 是个 input,那如果是一个其他的组件不就完蛋了嘛。

这是第一个问题,为了解决这个问题呢,Repeat 需要对它的 children 提两个条件:

  1. 属性上必须要接收一个 onChange 回调函数,函数接收一个对象参数,参数结构如下:

    {
        target: {
            value: ‘xxxx‘
        }
    }

    value 的值为当前项产出的数据,可能是个对象也可能是字符串或者数值。没错,我就是为了兼容 input event 的数据结构。你当然可以用任何你喜欢的且方便处理的数据结构。

  2. children 组件需要接收一个 value 属性,以展示其拥有的值。也就是说 children 组件应当是一个受控的(controlled)组件。

这就是一个协议,你希望某个组件内通过 Repeat 组件方便地添加多份并能获取到一组数据,那就必须要遵守这个协议。有同学可能会说为什么不搞的智能一点呢?嗯,这里我想分享一点个人经验:有些时候,尤其是在业务开发过程中,把公共部分抽取出来复用即可,点到为止,没有必要搞得那么“强大”,剩下的事情让一个很容易遵守的协议来完成,其实效率会更高,更容易让人理解。

其实在计算机的世界中处处充满了协议,例如你想让 HTTP Server 返回正确的响应,你必须要遵循 http 协议来和它通信;你生产的显卡能买的出去,必须要遵守相应的协议,要能插到别人家生产的主板上。

扯远了!收!

对,有了上面这个约定以后,Repeat 一行代码未加,是不是感觉功能完善了许多?嗯,就是这个目的。现在我们来实现一下文章开始时候说的第二个场景。

聪明的你一定已经知道该怎么做了,没错,只要我们实现一个 UserForm 组件,并让他满足上面的约定即可。请看代码:

class UserForm extends React.Component {
    handleFieldChange(e) {
        const { name, value } = e.target;
        const formData = {
            ...this.props.value,
            [name]: value,
        }
        this.props.onChange({
            target: {
                value: formData,
            }
        });
    }
    render() {
        const formData = this.props.value || {};
        return (
            <div>
                <div>
                    <label for="">姓名</label>
                    <input
                        type="text"
                        name="name"
                        value={formData.name}
                        onChange={e => this.handleFieldChange(e)}
                    />
                </div>
                <div>
                    <label for="">地址</label>
                    <input
                        type="text"
                        name="addr"
                        value={formData.addr}
                        onChange={e => this.handleFieldChange(e)}
                    />
                </div>
            </div>
        )
    }
}

为了让代码更简洁,我把 UserForm 这个组件实现为了一个支持受控的组件,但是在目前的业务场景下已经足够了,在实际情况下,你可以按需调整。

通过这个例子,还希望大家能体会到组件拆分的一个好处。就是,UserFormRepeat 拆分成两个组件以后,UserForm 的复用性会更强。可以想象一下,当用户被批量添加以后,是不是有可能在编辑单个用户的时候,可以继续使用这个组件。

好啦,关于第三个场景我想就没有必要再实现一遍了,Repeat 嵌套多少层其实都是可以的。

更进一步

实际上在实际应用中,Repeat 这个组件还需要做进一步完善,其中一个就是样式,还有可能在不同的场景下,虽然交互都是这样,但样式会有所差异。另外默认是“添加”、“移除”两个文字按钮,说不定实际业务场景中是两个 +,- 的图标按钮;还有可能“添加”、“移除”的位置为有所变化。

这些问题怎么处理呢?下面给大家描述下思路,具体代码就不写了,如果有什么疑问可以给我留言。

  1. 关于样式,你可以给 Repeat 添加 itemClassNamebuttonsClassName 两个属性分别为每一项和按钮区域的 css class。这样你就可以在不同的场景下指定不同的样式了。
  2. 关于如何将文字按钮改为图标按钮,你可以给 Repeat 添加 renderButtons 这样一个函数属性,如果未指定则用默认的方式渲染按钮,如果有则勇气返回值渲染属性。

最后

这是本篇文章的代码:https://codepen.io/Sarike/pen...

好啦,文章就到这吧,如果有什么疑问可以给我留言。谢谢大家,祝大家国庆、中秋节快乐。

原文地址:https://www.cnblogs.com/10yearsmanong/p/12207889.html

时间: 2024-11-06 23:35:21

React 开发实战(一)- Repeat 组件的相关文章

8、手把手教React Native实战之ReactJS组件生命周期

1.创建阶段 getDefaultProps:处理props的默认值 在React.createClass调用 2.实例化阶段 React.render(<HelloMessage 启动之后 getInitialState.componentWillMount.render.componentDidMount state:组件的属性,主要是用来存储组件自身需要的数据,每次数据的更新都是通过修改state属性的值,ReactJS内部会监听state属性的变化,一旦发生变化的话,就会主动触发组件的r

《React 开发实战》笔记(一)空格与注释

(一)如何添加空格 标签中如果需要打一个空格,需要使用{" "},如: return{ <a href="www.baidud.com" >百度</a> <a href="www.baidud.com" >引擎</a> } 输出:百度引擎 return{ <a href="www.baidud.com" >百度</a>{" "} <

东方耀 手把手教React Native实战开发视频教程+源码笔记全集

课程序号标题 第0课0.手把手教React Native实战之开山篇_视频 第1课1.手把手教React Native实战之环境搭建_视频_Windows环境 第1课1.手把手教React Native实战之环境搭建[Mac真机]同时调试开发Android&IOS 第2课2.手把手教React Native实战之从React到RN 第3课3.手把手教React Native实战之flexbox布局(RN基础) 第4讲4.手把手教React Native实战之flexbox布局(伸缩属性) 第5讲

React Native移动开发实战-3-实现页面间的数据传递

React Native使用props来实现页面间数据传递和通信.在React Native中,有两种方式可以存储和传递数据:props(属性)以及state(状态),其中: props通常是在父组件中指定的,而且一经指定,在被指定的组件的生命周期中则不再改变. state通常是用于存储需要改变的数据,并且当state数据发生更新时,React Native会刷新界面. 了解了props与state的区别之后,读者应该知道,要将首页的数据传递到下一个页面,需要使用props.所以,修改home.

&lt;React Native移动开发实战&gt;-1-React Native的JSX解决方案

JSX并不是一门新的开发语言,而是Facebook提出的语法方案:一种可以在JavaScript代码中直接书写HTML标签的语法糖,所以,JSX本质上还是JavaScript语言. 小知识:语法糖(Syntactic sugar)是由英国计算科学家彼得·兰丁(https://zh.wikipedia.org/ wiki/%E5%BD%BC%E5%BE%97%C2%B7%E5%85%B0%E4%B8%81)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序

React Native Android原生模块开发实战|教程|心得|如何创建React Native Android原生模块

尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691503) 前言 一直想写一下我在React Native原生模块封装方面的一些经验和心得,来分享给大家,但实在抽不开身,今天看了一下日历发现马上就春节了,所以就赶在春节之前将这篇博文写好并发布(其实是两篇:要看iOS篇的点这里<React Native iOS原生模块开发>). 我平时在用React Native开发App时会

React Native iOS原生模块开发实战|教程|心得|如何创建React Native iOS原生模块

尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691432) 前言 一直想写一下我在React Native原生模块封装方面的一些经验和心得,来分享给大家,但实在抽不开身,今天看了一下日历发现马上就春节了,所以就赶在春节之前将这篇博文写好并发布(其实是两篇:要看Android篇的点这里<React Native Android原生模块开发>). 我平时在用React Nativ

React Native Android原生模块开发实战|教程|心得|怎样创建React Native Android原生模块

尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691503) 告诉大家一个好消息.为大家精心准备的React Native视频教程公布了,大家现能够看视频学React Native了. 前言 一直想写一下我在React Native原生模块封装方面的一些经验和心得.来分享给大家,但实在抽不开身.今天看了一下日历发现立即就春节了.所以就赶在春节之前将这篇博文写好并公布(事实上是两篇

慕课网实战—《用组件方式开发 Web App全站 》笔记二

运用HTML5.CSS3.JS流行技术,采用组件式开发模式,开发Web App全站!技术大牛带你统统拿下不同类型的HTML5动态数据报告! <用组件方式开发 Web App全站 > 项目JS类开发 静态页思路验证 jQuery全屏滚动插件fullPage.js ??使用参考:Fullpage入门指南 组件切换,入场,出场动画 ???? DOM事件循环传播-无限循环 ??使用return false;或者triggerHander()方法阻止事件向上传播. 相关代码 HTML <body&