编写简洁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组件的技巧的资料请关注我们其它相关文章!

(0)

相关推荐

  • 吴恩达机器学习练习:SVM支持向量机

    1 Support Vector Machines 1.1 Example Dataset 1 %matplotlib inline import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sb from scipy.io import loadmat from sklearn import svm 大多数SVM的库会自动帮你添加额外的特征X₀已经θ₀,所以无需手动添加 ma

  • React中使用setInterval函数的实例

    本文是基于Windows 10系统环境,学习和使用React:Windows 10 一.setInterval函数 (1) 定义 setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式. setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭.由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数. (2) 实例 import React, { Component } fr

  • react实现Radio组件的示例代码

    本文旨在用最清楚的结构去实现一些组件的基本功能.希望和大家一起学习,共同进步 效果展示: 测试组件: class Test extends Component { constructor(props) { super(props) this.state = { active:1 } } onGroupChange(value) { this.setState({ active: value }) } render() { return ( <div> <RadioGroup onChan

  • React 错误边界组件的处理

    这是React16的内容,并不是最新的技术,但是用很少被讨论,直到通过文档发现其实也是很有用的一部分内容,还是总结一下- React中的未捕获的 JS 错误会导致整个应用的崩溃,和整个组件树的卸载.从 React16 开始就是这样.但是同时React也引入了一个新的概念--错误边界. 定义,是什么 错误边界仍然是一种组件,可以捕获(打印或者其他方式)处理该组件的子组件树任何位置的 JavaScript 错误,并根据需要渲染出备用UI. 工作方式类似于try-catch,但是错误边界只用于 Rea

  • 详解react setState

    setState是同步还是异步 自定义合成事件和react钩子函数中异步更新state 以在自定义click事件中的setState为例 import React, { Component } from 'react'; class Test extends Component { constructor(props) { super(props); this.state = { count: 1 }; } handleClick = () => { this.setState({ count:

  • 编写简洁React组件的小技巧

    本文源于翻译文章 Simple tips for writing clean React components, 原文作者 Iskander Samatov 在这篇文章中,我们会回顾一些简单的技巧,它们将帮助我们编写更简洁的 React 组件,并且更好地扩展我们的项目. 避免使用扩展操作符传递 props 首先,让我们从一个应该避免的反模式开始.除非有明确的理由这样做,否则应该避免在组件树中使用扩展操作符传递props,比如:{ ...props }. 通过这种方式传递 props 确实可以更快

  • 分享几个写简洁java代码的小技巧

    目录 1.定义配置文件信息 2.用@RequiredArgsConstructor代替@Autowired 3.不要返回null 4.ifelse 5.减少controller业务代码 6.将字符串数组转换成逗号分隔字符串 总结 1. 定义配置文件信息 有时候我们为了统一管理会把一些变量放到 yml 配置文件中 例如 用 @ConfigurationProperties 代替 @Value 使用方法 定义对应字段的实体 @Data // 指定前缀 @ConfigurationProperties

  • 详解React Native顶|底部导航使用小技巧

    导航一直是App开发中比较重要的一个组件,ReactNative提供了两种导航组件供我们使用,分别是:NavigatorIOS和Navigator,但是前者只能用于iOS平台,后者在ReactNative0.44版本以后已经被移除了. 好在有人提供了更好的导航组件,就是我们今天要讲的react-navigation,并且ReactNative官方更推荐我们使用此组件. 本篇文章只讲解基础用法,如果你想了解更多,请戳这里->戳我.  简介 react-navigation主要包括导航,底部tab,

  • React 小技巧教你如何摆脱hooks依赖烦恼

    react项目中,很常见的一个场景: const [watchValue, setWatchValue] = useState(''); const [otherValue1, setOtherValue1] = useState(''); const [otherValue2, setOtherValue2] = useState(''); useEffect(() => { doSomething(otherValue1, otherValue2); }, [watchValue, othe

  • 编写React组件项目实践分析

    当我刚开始写React的时候,我看过很多写组件的方法.一百篇教程就有一百种写法.虽然React本身已经成熟了,但是如何使用它似乎还没有一个"正确"的方法.所以我(作者)把我们团队这些年来总结的使用React的经验总结在这里.希望这篇文字对你有用,不管你是初学者还是老手. 开始前: 我们使用ES6.ES7语法如果你不是很清楚展示组件和容器组件的区别,建议您从阅读这篇文章开始如果您有任何的建议.疑问都清在评论里留言 基于类的组件 现在开发React组件一般都用的是基于类的组件.下面我们就来

  • iOS组件依赖避免冲突的小技巧分享

    问题缘由 本文以 YBImageBrowser[1] 组件举例. YBImageBrowser 依赖了 SDWebImage,在使用 CocoaPods 集成到项目中时,可能会出现一些依赖冲突的问题,最近社区提了多个 Issues 并且在 Insights -> Traffic -> Popular content 中看到了此类问题很高的关注度,所以不得不着手解决. 严格的版本限制 一个开源组件的迭代过程中,保证上层接口的向下兼容就不错了.为了优化性能并且控制内存,YBImageBrowser

  • 14个编写Spring MVC控制器的实用小技巧(吐血整理)

    本文介绍了编写Spring MVC框架的控制器(controller)的基础技巧和最佳操作.在Spring MVC框架中,编写控制器类通常是为了处理用户提出的请求. 编写完成后,控制器会调用一个业务类来处理业务相关任务,进而重定向客户到逻辑视图名.Springdispatcher servlet会对逻辑视图名进行解析,并渲染结果或输出.这就是一个典型的"请求-响应"的完整流程. 1.使用@controllerstereotype 创建一个能够处理单个或多个请求的控制器类,最简单的方法就

  • Xcode中代码注释编写的一些小技巧

    目录 前言 Objective-C的代码注释 Swift的代码注释 Objective-C和Swift的注释风格现在已经统一 快速修改注释 参考文档 总结 前言 码农总是在搬砖,日复一日,年复一年,有的时候都会麻木. 代码大家都会写,但是把注释写好却是一个技术活. 下面这段话,很好的说明了写好注释的感觉: 注释代码很像清洁你的厕所--你不想干,但如果你做了,这绝对会给你和你的客人带来更愉悦的体验.-- Ryan Campbell 今天给大家聊的就是在Xcode中,代码注释编写小技巧. Objec

  • 使用React组件编写温度显示器

    本文实例为大家分享了React组件编写温度显示器的具体代码,供大家参考,具体内容如下 这是模拟了一下温度显示器的效果,先看效果: 先在页面中引入React等: import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; // 页面的样式文件 开发过程是这样的: 首先定义一个BoillingVerdict组件,用来显示温度显示器的(样式先不写,

  • Kotlin开发的一些实用小技巧总结

    前言 随着Google I/O大会的召开,Google宣布将支持Kotlin作为Android的开发语言,最近关于Kotlin的文章.介绍就异常的活跃. 本文主要给大家介绍了关于Kotlin开发的一些实用小技巧,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 1.Lazy Loading(懒加载) 延迟加载有几个好处.延迟加载能让程序启动时间更快,因为加载被推迟到访问变量时. 这在使用 Kotlin 的 Android 应用程序而不是服务器应用程序中特别有用.对于 Androi

随机推荐