编写简洁React组件的小技巧

 更新时间:2021年04月08日 10:25:27   作者:KooFE前端团队  
这篇文章主要介绍了编写简洁React组件的小技巧,帮助大家更好的理解和学习使用React,感兴趣的朋友可以了解下

本文源于翻译文章 Simple tips for writing clean React components, 原文作者 Iskander Samatov

在这篇文章中,我们会回顾一些简单的技巧,它们将帮助我们编写更简洁的 React 组件,并且更好地扩展我们的项目。

避免使用扩展操作符传递 props

首先,让我们从一个应该避免的反模式开始。除非有明确的理由这样做,否则应该避免在组件树中使用扩展操作符传递props,比如:{ ...props }。

通过这种方式传递 props 确实可以更快的编写组件。但这也使得我们很难去定位代码中的 bug。会使我们对编写的组件失去信心,会使得我们重构组件变得更加困难,而且可能会导致出现很难排查的 bug。

将函数参数封装成一个对象

如果函数接收多个参数,最好将它们封装成一个对象。举个例子:

export const sampleFunction = ({ param1, param2, param3 }) => {
  console.log({ param1, param2, param3 });
}

以这种方式编写函数签名有几个显著的优点:

  1. 你不用再担心参数传递的顺序。我曾犯过几次因函数传参顺序问题而产生了 bug 的错误。
  2. 对于配置了智能提示的编辑器(现在的大多数都有),可以很好地完成函数参数的自动填充。

对于事件处理函数,将该处理函数作为函数的返回值

如果你熟悉函数式编程,这种编程技术类似于函数柯里化,因为已经提前设置了一些参数。

我们来看看这个例子:

import React from 'react'

export default function SampleComponent({ onValueChange }) {

  const handleChange = (key) => {
    return (e) => onValueChange(key, e.target.value)
  }

  return (
    <form>
      <input onChange={handleChange('name')} />
      <input onChange={handleChange('email')} />
      <input onChange={handleChange('phone')} />
    </form>
  )
}

如您所见,以这种方式编写处理程序函数,可以使组件树保持简洁。

组件渲染使用 map 而非 if/else

当你需要基于自定义逻辑呈现不同的元素时,我建议使用使用 map 而非 if/else 语句。

下面是一个使用if/else的示例:

import React from 'react'

const Student = ({ name }) => <p>Student name: {name}</p>
const Teacher = ({ name }) => <p>Teacher name: {name}</p>
const Guardian = ({ name }) => <p>Guardian name: {name}</p>

export default function SampleComponent({ user }) {
  let Component = Student;
  if (user.type === 'teacher') {
    Component = Teacher
  } else if (user.type === 'guardian') {
    Component = Guardian
  }

  return (
    <div>
      <Component name={user.name} />
    </div>
  )
}

下面是一个使用map的示例:

import React from 'react'

const Student = ({ name }) => <p>Student name: {name}</p>
const Teacher = ({ name }) => <p>Teacher name: {name}</p>
const Guardian = ({ name }) => <p>Guardian name: {name}</p>

const COMPONENT_MAP = {
  student: Student,
  teacher: Teacher,
  guardian: Guardian
}

export default function SampleComponent({ user }) {
  const Component = COMPONENT_MAP[user.type]

  return (
    <div>
      <Component name={user.name} />
    </div>
  )
}

使用这个简单的小策略,可以使你的组件变得更具有可读性,更容易理解。而且它还使逻辑扩展变得更简单。

Hook组件

只要不滥用,这个模式是很有用的。

你可能会发现自己在应用中使用了很多组件。如果它们需要一个状态来发挥作用,你可以将他们封装为一个 hook 提供该状态。这些组件的一些好例子是弹出框、toast 通知或简单的 modal 对话框。例如,下面是一个用于简单确认对话框的 hook 组件:

import React, { useCallback, useState } from 'react';
import ConfirmationDialog from 'components/global/ConfirmationDialog';

export default function useConfirmationDialog({
  headerText,
  bodyText,
  confirmationButtonText,
  onConfirmClick,
}) {
  const [isOpen, setIsOpen] = useState(false);

  const onOpen = () => {
    setIsOpen(true);
  };

  const Dialog = useCallback(
    () => (
      <ConfirmationDialog
        headerText={headerText}
        bodyText={bodyText}
        isOpen={isOpen}
        onConfirmClick={onConfirmClick}
        onCancelClick={() => setIsOpen(false)}
        confirmationButtonText={confirmationButtonText}
      />
    ),
    [isOpen]
  );

  return {
    Dialog,
    onOpen,
  };
}

你可以像这样使用 hook 组件:

import React from "react";
import { useConfirmationDialog } from './useConfirmationDialog'

function Client() {
  const { Dialog, onOpen } = useConfirmationDialog({
    headerText: "Delete this record?",
    bodyText:
      "Are you sure you want delete this record? This cannot be undone.",
    confirmationButtonText: "Delete",
    onConfirmClick: handleDeleteConfirm,
  });

  function handleDeleteConfirm() {
    //TODO: delete
  }

  const handleDeleteClick = () => {
    onOpen();
  };

  return (
    <div>
      <Dialog />
      <button onClick={handleDeleteClick} />
    </div>
  );
}

export default Client;

以这种方式提取组件可以避免编写大量状态管理的样板代码。如果你想了解更多 React hooks,请查看 我的帖子

组件拆分

下面三个技巧是关于如何巧妙地拆分组件。根据我的经验,保持组件的简洁是保持项目可管理的最佳方法。

使用包装器

如果你正在努力寻找一种方法来拆分复杂组件,看看你的组件中每个元素所提供的功能。有些元素提供了独特的功能,比如拖拽功能。

下面是一个使用react-beautiful-dnd实现拖拽的组件示例:

import React from 'react'
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
export default function DraggableSample() {
  function handleDragStart(result) { 
    console.log({ result });
  }
  function handleDragUpdate({ destination }) { 
    console.log({ destination });
  }
  const handleDragEnd = ({ source, destination }) => { 
    console.log({ source, destination });
  };
  return (
    <div>
      <DragDropContext
        onDragEnd={handleDragEnd}
        onDragStart={handleDragStart}
        onDragUpdate={handleDragUpdate}
      >
        <Droppable 
          droppableId="droppable"
          direction="horizontal"
        >
          {(provided) => (
            <div {...provided.droppableProps} ref={provided.innerRef}> 
              {columns.map((column, index) => {
                return (
                  <ColumnComponent
                    key={index}
                    column={column}
                  />
                );
              })}
            </div>
          )}
        </Droppable>
      </DragDropContext>
    </div>
  )
}

现在,看一下在我们将所有拖拽逻辑移到包装器之后的组件:

import React from 'react'
export default function DraggableSample() {
  return (
    <div>
      <DragWrapper> 
      {columns.map((column, index) => { 
        return (
          <ColumnComponent key={index} column={column}/>
        );
      })}
      </DragWrapper>
    </div>
  )
}

下面是包装器的代码:

import React from 'react'
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
export default function DragWrapper({children}) {
  function handleDragStart(result) { 
    console.log({ result });
  }
  function handleDragUpdate({ destination }) { 
    console.log({ destination });
  }
  const handleDragEnd = ({ source, destination }) => { 
    console.log({ source, destination });
  };
  return (
    <DragDropContext 
      onDragEnd={handleDragEnd}
      onDragStart={handleDragStart} 
      onDragUpdate={handleDragUpdate}
    >
      <Droppable droppableId="droppable" direction="horizontal"> 
        {(provided) => (
          <div {...provided.droppableProps}  ref={provided.innerRef}> 
            {children}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  )
}

因此,可以更直观地看到组件在更高层次上的功能。所有用于拖拽的功能都在包装器中,使得代码更容易理解。

关注点分离

这是我最喜欢的拆分较大组件的方法。

从 React 角度出发,关注点的分离意味着分离组件中负责获取和改变数据的部分和纯粹负责显示元素的部分。

这种分离关注点的方法是引入 hooks 的主要原因。你可以用自定义 hook 封装所有方法或全局状态连接的逻辑。

例如,让我们看看如下组件:

import React from 'react'
import { someAPICall } from './API' 
import ItemDisplay from './ItemDisplay'
export default function SampleComponent() { 
  const [data, setData] = useState([])
  useEffect(() => { 
    someAPICall().then((result) => { setData(result)})
  }, [])
  function handleDelete() { console.log('Delete!'); }
  function handleAdd() { console.log('Add!'); }
  const handleEdit = () => { console.log('Edit!'); };
  return (
    <div>
      <div>
        {data.map(item => <ItemDisplay item={item} />)} 
      </div>
      <div>
        <button onClick={handleDelete} /> 
        <button onClick={handleAdd} /> 
        <button onClick={handleEdit} /> 
      </div>
    </div>
  )
}

下面是它的重构版本,使用自定义hook拆分后的代码:

import React from 'react'
import ItemDisplay from './ItemDisplay'
export default function SampleComponent() {
  const { data, handleDelete, handleEdit, handleAdd } = useCustomHook()
  return (
    <div>
      <div>
        {data.map(item => <ItemDisplay item={item} />)} 
      </div>
      <div>
        <button onClick={handleDelete} /> 
        <button onClick={handleAdd} /> 
        <button onClick={handleEdit} /> 
      </div>
    </div>
  )
}

这是该 hook 本身的代码:

import { someAPICall } from './API'
export const useCustomHook = () => { 
  const [data, setData] = useState([])
  useEffect(() => { 
    someAPICall().then((result) => { setData(result)})
  }, [])
  function handleDelete() { console.log('Delete!'); }
  function handleAdd() { console.log('Add!'); }
  const handleEdit = () => { console.log('Edit!'); };
  return { handleEdit, handleAdd, handleDelete, data }
}

每个组件封装为一个单独的文件

通常大家会这样写代码:

import React from 'react'
export default function SampleComponent({ data }) {
  const ItemDisplay = ({ name, date }) => ( 
    <div>
      <h3>{name}</h3>
      <p>{date}</p>
    </div> 
  )
  return (
    <div>
      <div>
        {data.map(item => <ItemDisplay item={item} />)}
      </div>
    </div> 
  )
}

虽然用这种方式编写 React 组件没有什么大问题,但这并不是一个好的做法。将 ItemDisplay 组件移动到一个单独的文件可以使你的组件松散耦合,易于扩展。

在大多数情况下,要编写干净整洁的代码,需要注意并花时间遵循好的模式和避免反模式。因此,如果你花时间遵循这些模式,它有助于你编写整洁的 React 组件。我发现这些模式在我的项目中非常有用,希望你也这么做!

以上就是编写简洁React组件的小技巧的详细内容,更多关于编写React组件的技巧的资料请关注脚本之家其它相关文章!

相关文章

  • React 中的 setState 是同步还是异步

    React 中的 setState 是同步还是异步

    这篇文章主要介绍了React 中的 setState 是同步还是异步,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-06-06
  • React Fiber源码深入分析

    React Fiber源码深入分析

    Fiber 可以理解为一个执行单元,每次执行完一个执行单元,React Fiber就会检查还剩多少时间,如果没有时间则将控制权让出去,然后由浏览器执行渲染操作,这篇文章主要介绍了React Fiber架构原理剖析,需要的朋友可以参考下
    2022-11-11
  • React项目仿小红书首页保姆级实战教程

    React项目仿小红书首页保姆级实战教程

    React 是一个用于构建用户界面的 Javascript库,接下来将通过实战小红书首页的详细介绍其设计思路和方法,将读者带入到react的开源世界,需要的朋友可以参考下
    2022-07-07
  • 使用VScode 插件debugger for chrome 调试react源码的方法

    使用VScode 插件debugger for chrome 调试react源码的方法

    这篇文章主要介绍了使用VScode 插件debugger for chrome 调试react源码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • React Hooks - useContetx和useReducer的使用实例详解

    React Hooks - useContetx和useReducer的使用实例详解

    这篇文章主要介绍了React Hooks - useContetx和useReducer的基本使用,本文通过实例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-11-11
  • React-Native中一些常用组件的用法详解(二)

    React-Native中一些常用组件的用法详解(二)

    这篇文章主要跟大家介绍了关于React-Native中一些常用组件的用法的相关资料,其中详细介绍了关于ScrollView组件、ListView组件、Navigator组件、TableBarIOS组件以及网络请求等相关内容,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-06-06
  • React antd中setFieldsValu的简便使用示例代码

    React antd中setFieldsValu的简便使用示例代码

    form.setFieldsValue是antd Form组件中的一个方法,用于动态设置表单字段的值,它接受一个对象作为参数,对象的键是表单字段的名称,值是要设置的字段值,这篇文章主要介绍了React antd中setFieldsValu的简便使用,需要的朋友可以参考下
    2023-08-08
  • React 中的列表渲染要加 key的原因分析

    React 中的列表渲染要加 key的原因分析

    这篇文章主要介绍了React 中的列表渲染为什么要加 key,在 React 中我们经常需要渲染列表,比如展示好友列表,文中给大家介绍了列表渲染不提供 key 会如何,通过实例代码给大家介绍的非常详细,需要的朋友一起看看吧
    2022-07-07
  • React中如何设置自定义滚动条样式

    React中如何设置自定义滚动条样式

    这篇文章主要介绍了React中如何设置自定义滚动条样式问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • 详解react-router 4.0 下服务器如何配合BrowserRouter

    详解react-router 4.0 下服务器如何配合BrowserRouter

    这篇文章主要介绍了详解react-router 4.0 下服务器如何配合BrowserRouter,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12

最新评论