文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

一篇让我们学会React实践

2024-12-03 01:06

关注

每天都在写业务代码中度过,但是呢,经常在写业务代码的时候,会感觉自己写的某些代码有点别扭,但是又不知道是哪里别扭,今天这篇文章我整理了一些在项目中使用的一些小的技巧点。

状态逻辑复用

在使用React Hooks之前,我们一般复用的都是组件,对组件内部的状态是没办法复用的,而React Hooks的推出很好的解决了状态逻辑的复用,而在我们日常开发中能做到哪些状态逻辑的复用呢?下面我罗列了几个当前我在项目中用到的通用状态复用。

useRequest

为什么要封装这个hook呢?在数据加载的时候,有这么几点是可以提取成共用逻辑的

  1. const useRequest = () => { 
  2.   const [loading, setLoading] = useState(false); 
  3.   const [error, setError] = useState(); 
  4.  
  5.   const run = useCallback(async (...fns) => { 
  6.     setLoading(true); 
  7.     try { 
  8.       await Promise.all
  9.         fns.map((fn) => { 
  10.           if (typeof fn === 'function') { 
  11.             return fn(); 
  12.           } 
  13.           return fn; 
  14.         }) 
  15.       ); 
  16.     } catch (error) { 
  17.       setError(error); 
  18.     } finally { 
  19.       setLoading(false); 
  20.     } 
  21.   }, []); 
  22.  
  23.   return { loading, error, run }; 
  24. }; 
  25.  
  26. function App() { 
  27.   const { loading, error, run } = useRequest(); 
  28.   useEffect(() => { 
  29.     run( 
  30.       new Promise((resolve) => { 
  31.         setTimeout(() => { 
  32.           resolve(); 
  33.         }, 2000); 
  34.       }) 
  35.     ); 
  36.   }, []); 
  37.   return ( 
  38.     "App"
  39.        
  40.         <Table columns={columns} dataSource={data}>Table
  41.        
  42.     
 
  •   ); 
  • usePagination

    我们用表格的时候,一般都会用到分页,通过将分页封装成hook,一是可以介绍前端代码量,二是统一了前后端分页的参数,也是对后端接口的一个约束。

    1. const usePagination = ( 
    2.   initPage = { 
    3.     total: 0, 
    4.     current: 1, 
    5.     pageSize: 10, 
    6.   } 
    7. ) => { 
    8.   const [pagination, setPagination] = useState(initPage); 
    9.  
    10.   // 用于接口查询数据时的请求参数 
    11.   const queryPagination = useMemo( 
    12.     () => ({ limit: pagination.pageSize, offset: pagination.current - 1 }), 
    13.     [pagination.current, pagination.pageSize] 
    14.   ); 
    15.  
    16.   const tablePagination = useMemo(() => { 
    17.     return { 
    18.       ...pagination, 
    19.       onChange: (page, pageSize) => { 
    20.         setPagination({ 
    21.           ...pagination, 
    22.           current: page, 
    23.           pageSize, 
    24.         }); 
    25.       }, 
    26.     }; 
    27.   }, [pagination]); 
    28.  
    29.   const setTotal = useCallback((total) => { 
    30.     setPagination((prev) => ({ 
    31.       ...prev, 
    32.       total, 
    33.     })); 
    34.   }, []); 
    35.   const setCurrent = useCallback((current) => { 
    36.     setPagination((prev) => ({ 
    37.       ...prev, 
    38.       current
    39.     })); 
    40.   }, []); 
    41.  
    42.   return { 
    43.     // 用于antd 表格使用 
    44.     pagination: tablePagination, 
    45.     // 用于接口查询数据使用 
    46.     queryPagination, 
    47.     setTotal, 
    48.     setCurrent, 
    49.   }; 
    50. }; 

    除了上面示例的两个hook,其实自定义hook可以无处不在,只要有公共的逻辑可以被复用,都可以被定义为独立的hook,然后在多个页面或组件中使用,我们在使用redux,react-router的时候,也会用到它们提供的hook。

    在合适场景给useState传入函数

    我们在使用useState的setState的时候,大部分时候都会给setState传入一个值,但实际上setState不但可以传入普通的数据,而且还可以传入一个函数。下面极端代码分别描述了几个传入函数的例子。

    下面的代码3秒后输出什么?

    如下代码所示,也有有两个按钮,一个按钮会在点击后延迟三秒然后给count + 1, 第二个按钮会在点击的时候,直接给count + 1,那么假如我先点击延迟的按钮,然后多次点击不延迟的按钮,三秒钟之后,count的值是多少?

    1. import { useState, useEffect } from 'react'
    2.  
    3. function App() { 
    4.   const [count, setCount] = useState(0); 
    5.  
    6.   function handleClick() { 
    7.     setTimeout(() => { 
    8.       setCount(count + 1); 
    9.     }, 3000); 
    10.   } 
    11.  
    12.   function handleClickSync() { 
    13.     setCount(count + 1); 
    14.   } 
    15.  
    16.   return ( 
    17.     "App"
    18.       
      count:{count}
       
    19.       延迟加一 
    20.       加一 
    21.     
     
  •   ); 
  •  
  • export default App; 
  • 我们知道,React的函数式组件会在自己内部的状态或外部传入的props发生变化时,做重新渲染的动作。实际上这个重新渲染也就是重新执行这个函数式组件。

    当我们点击延迟按钮的时候,因为count的值需要三秒后才会改变,这时候并不会重新渲染。然后再点击直接加一按钮,count值由1变成了2, 需要重新渲染。这里需要注意的是,虽然组件重新渲染了,但是setTimeout是在上一次渲染中被调用的,这也意味着setTimeout里面的count值是组件第一次渲染的值。

    所以即使第二个按钮加一多次,三秒之后,setTimeout回调执行的时候因为引用的count的值还是初始化的0, 所以三秒后count + 1的值就是1

    如何让上面的代码延迟三秒后输出正确的值?

    这时候就需要使用到setState传入函数的方式了,如下代码:

    1. import { useState, useEffect } from 'react'
    2.  
    3. function App() { 
    4.   const [count, setCount] = useState(0); 
    5.  
    6.   function handleClick() { 
    7.     setTimeout(() => { 
    8.       setCount((prevCount) => prevCount + 1); 
    9.     }, 3000); 
    10.   } 
    11.  
    12.   function handleClickSync() { 
    13.     setCount(count + 1); 
    14.   } 
    15.  
    16.   return ( 
    17.     "App"
    18.       
      count:{count}
       
    19.       延迟加一 
    20.       加一 
    21.      
    22.   ); 
    23.  
    24. export default App; 

    从上面代码可以看到,setCount(count + 1)被改为了setCount((prevCount) => prevCount + 1)。我们给setCount传入一个函数,setCount会调用这个函数,并且将前一个状态值作为参数传入到函数中,这时候我们就可以在setTimeout里面拿到正确的值了。

    还可以在useState初始化的时候传入函数

    看下面这个例子,我们有一个getColumns函数,会返回一个表格的所以列,同时有一个count状态,每一秒加一一次。

    1. function App() { 
    2.   const columns = getColumns(); 
    3.   const [count, setCount] = useState(0); 
    4.   useEffect(() => { 
    5.     setInterval(() => { 
    6.       setCount((prevCount) => prevCount + 1); 
    7.     }, 1000); 
    8.   }, []); 
    9.  
    10.   useEffect(() => { 
    11.     console.log('columns发生了变化'); 
    12.   }, [columns]); 
    13.   return ( 
    14.     "App"
    15.       
      count: {count}
       
    16.       <Table columns={columns}>Table
    17.      
    18.   ); 

    上面的代码执行之后,会发现每次count发生变化的时候,都会打印出columns发生了变化,而columns发生变化便意味着表格的属性发生变化,表格会重新渲染,这时候如果表格数据量不大,没有复杂处理逻辑还好,但如果表格有性能问题,就会导致整个页面的体验变得很差?其实这时候解决方案有很多,我们看一下如何用useState来解决呢?

    1. // 将columns改为如下代码 
    2. const [columns] = useState(() => getColumns()); 

    这时候columns的值在初始化之后就不会再发生变化了。有人提出我也可以这样写 useState(getColumns()), 实际这样写虽然也可以,但是假如getColumns函数自身存在复杂的计算,那么实际上虽然useState自身只会初始化一次,但是getColumn还是会在每次组件重新渲染的时候被执行。

    上面的代码也可以简化为

    1. const [columns] = useState(getColumns); 

    了解hook比较算法的原理

    1. const useColumns = (options) => { 
    2.   const { isEdit, isDelete } = options; 
    3.   return useMemo(() => { 
    4.     return [ 
    5.       { 
    6.         title: ''
    7.         dataIndex: 'title'
    8.         key'title'
    9.       }, 
    10.       { 
    11.         title: '操作'
    12.         dataIndex: 'action'
    13.         key'action'
    14.         render() { 
    15.           return ( 
    16.             <> 
    17.               {isEdit && 
    18.               {isDelete && 
    19.              
    20.           ); 
    21.         }, 
    22.       }, 
    23.     ]; 
    24.   }, [options]); 
    25. }; 
    26.  
    27. function App() { 
    28.   const columns = useColumns({ isEdit: true, isDelete: false }); 
    29.   const [count, setCount] = useState(1); 
    30.  
    31.   useEffect(() => { 
    32.     console.log('columns变了'); 
    33.   }, [columns]); 
    34.   return ( 
    35.     "App"
    36.       
       
    37.          setCount(count + 1)}>修改count:{count
    38.        
    39.       <Table columns={columns} dataSource={[]}>Table
    40.      
    41.   ); 

    如上面的代码,当我们点击按钮修改count的时候,我们期待只有count的值会发生变化,但是实际上columns的值也发生了变化。想了解为什么columns会发生变化,我们先了解一下react比较算法的原理。

    react比较算法底层是使用的Object.is来比较传入的state的.

    语法: Object.is(value1, value2);

    如下代码是Object.is比较不同数据类型的数据时的返回值:

    1. Object.is('foo''foo');     // trueObject.is(window, window);   // trueObject.is('foo''bar');     // falseObject.is([], []);           // falsevar foo = { a: 1 };var bar = { a: 1 };Object.is(foo, foo);         // trueObject.is(foo, bar);         // falseObject.is(nullnull);       // true// 特例Object.is(0, -0);            // falseObject.is(0, +0);            // trueObject.is(-0, -0);           // trueObject.is(NaN, 0/0);         // true 

    通过上面的代码可以看到,Object.is对于对象的比较是比较引用地址的,而不是比较值的,所以Object.is([], []), Object.is({},{})的结果都是false。而对于基础类型来说,大家需要注意的是最末尾的四个特列,这是与===所不同的。

    再回到上面代码的例子中,useColumns将传入的options作为useMemo的第二个参数,而options是一个对象。当组件的count状态发生变化的时候,会重新执行整个函数组件,这时候useColumns会被调用然后传入{ isEdit: true, isDelete: false },这是一个新创建的对象,与上一次渲染所创建的options的内容虽然一致,但是Object.is比较结果依然是false,所以columns的结果会被重新创建返回。

    通过二次封装标准化组件

    我们在项目中使用antd作为组件库,虽然antd可以满足大部分的开发需要,但是有些地方通过对antd进行二次封装,不仅可以减少开发代码量,而且对于页面的交互起到了标准化作用。

    看一下下面这个场景, 在我们开发一个数据表格的时候,一般会用到哪些功能呢?

    1. 表格可以分页
    2. 表格最后一列会有操作按钮
    3. 表格顶部会有搜索区域
    4. 表格顶部可能会有操作按钮

    还有其他等等一系列的功能,这些功能在系统中会大量使用,而且其实现方式基本是一致的,这时候如果能把这些功能集成到一起封装成一个标准的组件,那么既能减少代码量,而且也会让页面展现上更加统一。

    以封装表格操作列为例,一般用操作列我们会像下面这样封装

    1. const columns = [{        title: '操作',        dataIndex: 'action',        key'action',        width: '10%',        align: 'center',        render: (_, row) => {          return (            <>              "link" onClick={() => handleEdit(row)}>                编辑                            "确认要删除?" onConfirm={() => handleDelete(row)}>                "link">删除                                    );        }      }] 

    我们期望的是操作列也可以像表格的columns一样通过配置来生成,而不是写jsx。看一下如何封装呢?

    1. // 定义操作按钮export interface IAction extends Omit'onClick'> {  // 自定义按钮渲染  render?: (row: anyindex: number) => React.ReactNode;  onClick?: (row: anyindex: number) => void;  // 是否有确认提示  confirm?: boolean;  // 提示文字  confirmText?: boolean;  // 按钮显示文字  text: string;}// 定义表格列export interface IColumnany> extends ColumnType {  actions?: IAction[];}// 然后我们可以定义一个hooks,专门用来修改表格的columns,添加操作列const useActionButtons = (  columns: IColumn[],  actions: IAction[] | undefined): IColumn[] => {  return useMemo(() => {    if (!actions || actions.length === 0) {      return columns;    }    return [      ...columns,      {        align: 'center',        title: '操作',        key'__action',        dataIndex: '__action',        width: Math.max(120, actions.length * 85),        render(value: any, row: anyindex: number) {          return actions.map((item) => {            if (item.render) {              return item.render(row, index);            }            if(item.confirm) {              return '确认要删除?'}                       onConfirm={() => item.onClick?.(row, index)}>                "link">{item.text}                          }            return (              "link"                key={item.text}                onClick={() => item.onClick?.(row, index)}              >                {item.text}                          );          });        }      }    ];  }, [columns, actions, actionFixed]);};// 最后我们对表格再做一个封装const CustomTable: React.FC = ({  actions,  columns,  ...props}) => {  const actionColumns = useActionColumns(columns,actions)  // 渲染表格} 

    通过上面的封装,我们再使用表格的时候,就可以这样去写

    1. const actions: IAction[] = [    {      text: '编辑',      onClick: handleModifyRecord,    },  ];return  

     本文转载自微信公众号「前端有的玩」,可以通过以下二维码关注。转载本文请联系前端有的玩公众号。

     

    来源:前端有的玩内容投诉

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    软考中级精品资料免费领

    • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

      难度     813人已做
      查看
    • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

      难度     354人已做
      查看
    • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

      难度     318人已做
      查看
    • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

      难度     435人已做
      查看
    • 2024年上半年系统架构设计师考试综合知识真题

      难度     224人已做
      查看

    相关文章

    发现更多好内容

    猜你喜欢

    AI推送时光机
    位置:首页-资讯-后端开发
    咦!没有更多了?去看看其它编程学习网 内容吧
    首页课程
    资料下载
    问答资讯