当前位置: 首页 > news >正文

百万网站建设报价搜索点击软件

百万网站建设报价,搜索点击软件,网站地图做几个,提升网站建设品质信息React学习笔记(番外二)——列表多选批量操作复合组件前言〇、Show you the code一、 任务分析及拆解表头行的Checkbox——总开关记录行的Checkbox——行级开关二、 基础实现表头行的文件——header-row.js记录行的文件——record-row.js页面的文件App.js…

React学习笔记(番外二)——列表多选批量操作复合组件

  • 前言
  • 〇、Show you the code
  • 一、 任务分析及拆解
    • 表头行的Checkbox——总开关
    • 记录行的Checkbox——行级开关
  • 二、 基础实现
    • 表头行的文件——header-row.js
    • 记录行的文件——record-row.js
    • 页面的文件App.js
    • 阶段效果
  • 三、 完善控制逻辑
    • custom-hooks.js——自定义React Hook
      • 4处useState
      • 4处useCallback
      • 1处useMemo
    • App.js——部分改造
    • header-row.js——部分改造
    • record-row.js——部分改造
    • 阶段效果
  • 四、 最后一步
  • 后记

前言

近期有需求需要实现如题的列表多选批量处理复合组件。因为是两部分组件互相控制,对于初次实现的我来说还是有一定困难的。经过几天的实现、Bugfix,现对这种比较常见的复合组件的实现作以记录。


〇、Show you the code

成熟的程序猿讲究【Talk is cheap, show me the code】,如果你只想看代码:

本篇文章中,最终的完整代码已托管在CodeSandBox,点击前往,查看代码及编译、渲染结果


一、 任务分析及拆解

先来看一下上面说的到底是什么。如下图,当我们有一个比较长的列表时,有时希望一次性多选几行,进行批量操作。
请添加图片描述

我们可以很轻松地将所需的复合组件拆分为以下几部分子组件:

  • 表头行的Checkbox——总开关
  • 记录行的Checkbox——行级开关
  • 右上方的操作按钮

表头行的Checkbox——总开关

这个Checkbox比较特殊,有3个状态,如下图:
Checkbox的3种状态

  1. 未选中状态: 点击后变为全部选中状态。此状态下,列表中的记录都是未选中状态。表头行Checkbox由于点击达到该状态时所有记录行Checkbox都变为未选中状态,所有记录行Checkbox由于点击都达到未选中状态时,表头行Checkbox也受控变为未选中状态。
  2. 部分选中状态: 点击后变为全部选中状态。此状态下,列表中有部分记录([1, n-1])为选中状态。该状态只能由部分记录行Checkbox受到点击变为选中状态而达到。
  3. 全部选中状态: 点击后变为未选中状态。此状态下,列表中的记录都是选中状态。表头行Checkbox由于点击达到该状态时所有记录行Checkbox都变为选中状态,所有记录行Checkbox由于点击都达到选中状态时,表头行Checkbox也受控变为全部选中状态。

记录行的Checkbox——行级开关

记录行前的就是常规的Checkbox,只有两种状态,如下图:
Checkbox的2种状态

  1. 未选中状态: 点击后变为选中状态。此状态下,当前行的记录是未选中状态。所有记录行Checkbox都为该状态时表头行Checkbox受控变为未选中状态。
  2. 选中状态: 点击后变为未选中状态。此状态下,当前行的记录是选中状态。部分记录行Checkbox为该状态时表头行Checkbox受控变为部分选中状态。所有记录行Checkbox都为该状态时表头行Checkbox受控变为全部选中状态。

二、 基础实现

我们将一个包含列表的页面分为三个部分:

  • 表头行的文件——header-row.js
  • 记录行的文件——record-row.js
  • 页面的文件——App.js

接下来分别实现三个部分

表头行的文件——header-row.js

import "./index.css";
import React from "react";
import { Checkbox } from "antd";const HeaderRow = () => {return (<div className="header-row"><div className="column0"><Checkbox /></div><div className="column1">Id</div><div className="column2">Title</div><div className="column3">Desc</div></div>);
};export default HeaderRow;

记录行的文件——record-row.js

import "./index.css";
import React from "react";
import { Checkbox } from "antd";const RecordRow = (props) => {let { record } = props;let { id, title, desc } = record;return (<div className="record-row"><div className="column0"><Checkbox /></div><div className="column1">{id}</div><div className="column2">{title}</div><div className="column3">{desc}</div></div>);
};export default RecordRow;

页面的文件App.js

import "./styles.css";
import React, { useMemo } from "react";
import HeaderRow from "./components/header-row";
import RecordRow from "./components/record-row";const MainPage = (props) => {// 生成要显示的记录数据,现实中应该通过后台请求获得let records = useMemo(() => {let _records = [];for (let i = 0; i < 10; i++) {_records.push({id: i + 1,title: `这是测试标题${i + 1}`,desc: `这是测试描述${i + 1}`});}return _records;}, []);// 记录行的组件列表let recordRowViews = useMemo(() => {return records.map((item) => {return <RecordRow key={`record-${item.id}`} record={item} />;});}, [records]);return (<div><HeaderRow />{recordRowViews}</div>);
};export default MainPage;

阶段效果

基础实现比较简单,当然功能也不完善。我们还没有写Checkbox之间的控制逻辑,先看看目前的效果:
基础实现操作动图


三、 完善控制逻辑

因为实现中涉及到多个页面具有同样的控件和逻辑需要,因此实现中尽可能地考虑代码的复用性。这里的代码实现拆分为以下几个部分:

  • custom-hooks.js——包含几个自定义React Hook,最主要的Checkbox互相控制算法逻辑部分
  • App.js——前文中已有的文件,做部分改造
  • header-row.js——前文中已有的文件,做部分改造
  • record-row.js——前文中已有的文件,做部分改造

custom-hooks.js——自定义React Hook

import { useState, useMemo, useCallback } from "react";/*** 列表多选,批量处理的逻辑Hook* @param showedRecords 经过过滤后页面需要显示的列表项* @returns {[[string],boolean,boolean,boolean,function,function]}*/
const useBatchOperation = (allRecords) => {// 表示部分[1, n-1]行被勾选的状态,仅控制UIlet [partialChecked, setPartialChecked] = useState(false);// 表示是否所有的条目当前都被选中(即UI表现为表头的checkbox是否勾选)let [headerChecked, setHeaderChecked] = useState(false);// 表示当前用户是否主动点击了表头的那一个checkbox,点击后无论状态如何,其他checkbox都要跟随该状态,同headerChecked配合控制使用let [operateAll, setOperateAll] = useState(false);// 当前勾选的行所代表的数据单元组成的列表let [checkedItemList, setCheckedItemList] = useState([]);// 选中某行记录let checkItem = useCallback((item) => {let newCheckedItemList = [...checkedItemList];if (!Array.isArray(item)) {item = [item];}for (let i = 0; i < item.length; i++) {let index = checkedItemList.indexOf(item[i]);if (index === -1) {newCheckedItemList.push(item[i]);}}setCheckedItemList(newCheckedItemList);return newCheckedItemList;},[checkedItemList]);// 取消选中某行记录let uncheckItem = useCallback((item, clear = false) => {let newCheckedItemList = [];if (!clear) {if (!Array.isArray(item)) {item = [item];}for (let i = 0; i < checkedItemList.length; i++) {if (item.indexOf(checkedItemList[i]) === -1) {newCheckedItemList.push(checkedItemList[i]);}}}setCheckedItemList(newCheckedItemList);return newCheckedItemList;},[checkedItemList]);// 记录行checkbox的onChange事件let onSingleCheckBoxChange = useCallback((checked, record) => {// 按照判断逻辑,必须放在第一行setOperateAll(false);let newCheckedItemList;if (checked) {newCheckedItemList = checkItem(record);} else {newCheckedItemList = uncheckItem(record);}setPartialChecked(newCheckedItemList.length > 0 &&newCheckedItemList.length < allRecords.length);setHeaderChecked(newCheckedItemList.length === allRecords.length);},[checkItem, uncheckItem, allRecords]);// 表头行checkbox的onChange事件let onBatchCheckBoxChange = useCallback(() => {if (!headerChecked) {checkItem(allRecords);} else {uncheckItem(null, true);}setPartialChecked(false);setHeaderChecked(!headerChecked);setOperateAll(true);}, [headerChecked, checkItem, uncheckItem, allRecords]);// 选中的记录,其Id组成的列表let checkedIdList = useMemo(() => {if (checkedItemList.length > 0) {return checkedItemList.map((item) => {return item.id;});}return [];}, [checkedItemList]);return [checkedIdList,partialChecked,operateAll,headerChecked,onSingleCheckBoxChange,onBatchCheckBoxChange];
};export { useBatchOperation };

这里代码量有110行,还是比较多的,我们拆开来讲一下逻辑。一共用了3种React Hook,分别是useStateuseCallbackuseMemo。其中useState4处,useCallback4处,useMemo1处。

4处useState

  // 表示部分[1, n-1]行被勾选的状态,仅控制UIlet [partialChecked, setPartialChecked] = useState(false);// 表示是否所有的条目当前都被选中(即UI表现为表头的checkbox是否勾选)let [headerChecked, setHeaderChecked] = useState(false);// 表示当前用户是否主动点击了表头的那一个checkbox,点击后无论状态如何,其他checkbox都要跟随该状态,同headerChecked配合控制使用let [operateAll, setOperateAll] = useState(false);// 当前勾选的行所代表的数据单元组成的列表let [checkedItemList, setCheckedItemList] = useState([]);
  • partialChecked: 标识表头行Checkbox当前是否处于部分选中状态,仅用于控制UI样式,初始值为false
  • headerChecked: 标识表头行Checkbox当前是否处于全部选中状态,即是否所有记录行Checkbox都为选中状态,初始值为false
  • operateAll: 标识当前用户是否主动点击了表头行Checkbox,点击后所有记录行Checkbox必须跟随改变为同样的状态,初始值为false
  • checkedItemList: 当前勾选的记录行所代表的数据组成的列表,前文段落页面的文件App.jsrecords数组的子集,初始值为空数组[]

4处useCallback

代码较长,不再复制徒增篇幅。

  • checkItem: 勾选某行记录行Checkbox后执行的事件方法。实际代码逻辑是给checkedItemList列表中新增一个记录数据,然后通过setCheckedItemList更新最新的checkedItemList,之所以使用useCallback封装一层,是为了其访问到的checkedItemList是当前最新的。
  • uncheckItem:checkItem作用相反,取消勾选某行记录行Checkbox后执行的事件方法。实际代码逻辑是在checkedItemList列表中找到并删除一个记录数据…
  • onSingleCheckBoxChange:记录行Checkbox被点击而发生状态变化时执行的事件方法。代码逻辑包括:根据当前状态是选中还是未选中调用checkItemuncheckItem更新checkedItemList,根据checkedItemListlength判断表头行Checkbox是否应该改为部分选中全部选中未选中状态等。
  • onBatchCheckBoxChange :表头行Checkbox被点击而发生状态变化时执行的事件方法。代码逻辑包括:根据当前状态是全部选中还是未选中调用checkItemuncheckItem更新checkedItemList等。

1处useMemo

  • checkedIdList: 根据checkedItemList实时计算,表示当前被选中的记录的id组成的列表,暴露出去,向后台发请求时用。

App.js——部分改造

......
const MainPage = (props) => {......// 使用自定义hook-useBatchOperation,传入records列表let [checkedIdList,partialChecked,operateAll,headerChecked,onSingleCheckBoxChange,onBatchCheckBoxChange] = useBatchOperation(records);let recordRowViews = useMemo(() => {return records.map((item) => {return (<RecordRowkey={`record-${item.id}`}record={item}operateAll={operateAll}headerChecked={headerChecked}onSingleCheckBoxChange={onSingleCheckBoxChange}/>);});}, [records, operateAll, headerChecked, onSingleCheckBoxChange]);return (<div><HeaderRowpartialChecked={partialChecked}headerChecked={headerChecked}onBatchCheckBoxChange={onBatchCheckBoxChange}/>{recordRowViews}</div>);
};export default MainPage;

为了不占过多篇幅,用......代替未发生改变的部分。

主要修改3个部分:

  1. 创建一个自定义Hook-useBatchOperation的实例,获得暴露出来的6个变量。
  2. RecrodRow传入operateAllheaderCheckedonSingleCheckBoxChange
  3. HeaderRow传入partialCheckedheaderCheckedonBatchCheckBoxChange

剩下暂时未用到的checkedIdList留给后面的batch-operation-buttons.js

header-row.js——部分改造

......
const HeaderRow = (props) => {let { partialChecked, headerChecked, onBatchCheckBoxChange } = props;......<Checkboxindeterminate={partialChecked}checked={headerChecked}onChange={onBatchCheckBoxChange}/>......);
};export default HeaderRow;

主要修改2个部分:

  1. 引入父组件App.js传入的partialCheckedheaderCheckedonBatchCheckBoxChange
  2. 将上述值和方法传入Checkbox控件

record-row.js——部分改造

......
const RecordRow = (props) => {......let { record, operateAll, headerChecked, onSingleCheckBoxChange } = props;let [checked, setChecked] = useState(false);useEffect(() => {if (operateAll) {setChecked(headerChecked);}}, [operateAll, headerChecked, setChecked]);let wrappedOnChange = useCallback(() => {setChecked(!checked);onSingleCheckBoxChange(!checked, record);}, [checked, setChecked, record, onSingleCheckBoxChange]);return (......<Checkboxchecked={checked}onChange={wrappedOnChange}onClick={wrappedOnChange}/>......);
};export default RecordRow;

主要修改4个部分:

  1. 引入父组件App.js传入的operateAllheaderCheckedonSingleCheckBoxChange
  2. 添加一个useEffect Hook,如果表头行Checkbox被点击,跟随其改变状态
  3. onSingleCheckBoxChange封装一层,被点击时不仅要更新自身状态,还要触发自定义Hook的数据更新
  4. 将上述值和方法传入Checkbox控件

阶段效果

  • 全选 / 取消全选
    请添加图片描述
  • 部分选中
    请添加图片描述

四、 最后一步

经过前文中的努力,UI上表现达到预期了,所需的数据(checkedIdList)也拿到了,距离实现文章开头的效果还差右上角的操作按钮。这里我们新建一个batch-operation-buttons.js

import "./index.css";
import React, { useCallback } from "react";const BatchOperationButtons = (props) => {let { checkedIdList } = props;let handleClick = useCallback((func) => {switch (func) {case 0:alert(`批量删除记录,IdList:${JSON.stringify(checkedIdList)}`);break;case 1:alert(`批量下载记录,IdList:${JSON.stringify(checkedIdList)}`);break;default:throw new TypeError(`操作码[${func}]未实现!`);}},[checkedIdList]);if (checkedIdList?.length < 1) {return null;}return (<div className="batch-operation-buttons"><span className="delete-button" onClick={() => handleClick(0)}>删除</span><span>/</span><span className="download-button" onClick={() => handleClick(1)}>下载</span></div>);
};export default BatchOperationButtons;

注意: 这里的alert是为了演示方便,现实情况下应该改为向后台发请求,按照checkedIdList和操作的func对选中的记录进行批量操作.

App.js中仅需改动以下代码即可

  ......return (<div><div className="buttons"><BatchOperationButtons checkedIdList={checkedIdList} /></div>......</div>);

后记

这次的实现中首次学习并使用了自定义Hook。实现之后发现了它的重要意义,即我们可以通过自定义Hook真正地将HTML模板和JS数据逻辑拆分为两个独立的文件,一个只管模板内容,一个只管数据的更新。官方一点的说法即:实现了Model(custom-hooks.js)View(App.js)的分离,意义重大。

为了便于理解,你可以想象,在学会使用自定义Hook之前,App.jscustom-hooks.js里的代码统统写在App.js里,这样一个控件里的代码量将非常大,维护起来比较麻烦。拆分为两个文件后,模板有Bug我们就只改View文件,数据有Bug我们就只改Model文件。


文章转载自:
http://hurlbat.fznj.cn
http://futurology.fznj.cn
http://cabob.fznj.cn
http://weeping.fznj.cn
http://acid.fznj.cn
http://conversus.fznj.cn
http://hopcalite.fznj.cn
http://tricarpellate.fznj.cn
http://tetanal.fznj.cn
http://imaret.fznj.cn
http://rostov.fznj.cn
http://ampullae.fznj.cn
http://osier.fznj.cn
http://unyielding.fznj.cn
http://algesia.fznj.cn
http://rasher.fznj.cn
http://tash.fznj.cn
http://aquiprata.fznj.cn
http://reduced.fznj.cn
http://flawless.fznj.cn
http://incapacitator.fznj.cn
http://righteous.fznj.cn
http://misdiagnosis.fznj.cn
http://thrang.fznj.cn
http://ibex.fznj.cn
http://purpura.fznj.cn
http://symphonism.fznj.cn
http://animalcule.fznj.cn
http://preeminent.fznj.cn
http://cryoprotective.fznj.cn
http://callboard.fznj.cn
http://trusting.fznj.cn
http://bichlorid.fznj.cn
http://canoodle.fznj.cn
http://grayer.fznj.cn
http://moonshiny.fznj.cn
http://rampike.fznj.cn
http://touchpen.fznj.cn
http://greenmail.fznj.cn
http://salon.fznj.cn
http://bauxitic.fznj.cn
http://arabia.fznj.cn
http://areopagitic.fznj.cn
http://tragedienne.fznj.cn
http://semiclassical.fznj.cn
http://peripeteia.fznj.cn
http://kurrajong.fznj.cn
http://maythorn.fznj.cn
http://rooming.fznj.cn
http://icsu.fznj.cn
http://donnard.fznj.cn
http://urinous.fznj.cn
http://keenness.fznj.cn
http://homogenate.fznj.cn
http://phosphatidyl.fznj.cn
http://transplanter.fznj.cn
http://disaffection.fznj.cn
http://auc.fznj.cn
http://indolent.fznj.cn
http://convertaplane.fznj.cn
http://limewater.fznj.cn
http://sequentially.fznj.cn
http://trashy.fznj.cn
http://prejudicious.fznj.cn
http://decadent.fznj.cn
http://festilogy.fznj.cn
http://barratry.fznj.cn
http://wayang.fznj.cn
http://piliferous.fznj.cn
http://kleptocracy.fznj.cn
http://columbine.fznj.cn
http://pectate.fznj.cn
http://summary.fznj.cn
http://israelitish.fznj.cn
http://busiest.fznj.cn
http://lanthanon.fznj.cn
http://ferdus.fznj.cn
http://oversweep.fznj.cn
http://liechtensteiner.fznj.cn
http://verbalism.fznj.cn
http://spiffing.fznj.cn
http://tellurium.fznj.cn
http://overfree.fznj.cn
http://blues.fznj.cn
http://zootoxin.fznj.cn
http://sputnik.fznj.cn
http://interlinear.fznj.cn
http://fireballing.fznj.cn
http://coccid.fznj.cn
http://rikisha.fznj.cn
http://hairstreak.fznj.cn
http://humanistic.fznj.cn
http://intertidal.fznj.cn
http://mcat.fznj.cn
http://zach.fznj.cn
http://droningly.fznj.cn
http://deflex.fznj.cn
http://preovulatory.fznj.cn
http://turpeth.fznj.cn
http://crustless.fznj.cn
http://www.dt0577.cn/news/100307.html

相关文章:

  • 国外设计网站pinterest设计网址网络营销渠道类型有哪些
  • 慈溪专业做网站公司搜索广告是什么
  • 邯郸网络名称抖音搜索seo代理
  • 如何在电影网站中做淘客google chrome网页版
  • 秦皇岛做网站优化公司长沙网站搭建优化
  • 网站做赌博做任务汤阴县seo快速排名有哪家好
  • 北京会所网站推广互联网营销方案策划
  • 温州哪里有做网站环球网疫情最新
  • 网站建设及推广的书谷歌搜索引擎优化seo
  • 怎么知道网站有没有做301重定向谷歌google官网下载
  • 电子商务网站建设规划书的内容seo网络优化培训
  • html 网站 模板广告公司怎么找客户资源
  • 卸载wordpress插件郑州厉害的seo顾问公司
  • 自己做的网站怎么设置地址游戏app拉新平台
  • 靖江做网站的天气预报最新天气预报
  • 夏邑县城乡建设规划局网站建网站的流程
  • tomcat做的网站打不开了网站优化关键词
  • 易班网站的建设内容网站设计用什么软件
  • 桂林网站建设郑州seo技术博客
  • 上海兼职网站制作深圳网站优化软件
  • 12355能找回智慧团建密码吗福清市百度seo
  • wordpress 多语言建站seo专业培训技术
  • 长沙做网站设计网站seo搜索引擎优化教程
  • 具有品牌的做网站seo百家论坛
  • 淄博网站推广那家好seo的搜索排名影响因素有哪些
  • 成品网站怎样建设seo关键词有话要多少钱
  • 西安做网站公司seo是搜索引擎吗
  • 湘潭企业seo优化哪家好seo顾问阿亮
  • 上地网站建设关键词免费下载
  • 牛牛网站开发2023北京封控了