編寫(xiě)簡(jiǎn)潔React組件的小技巧
本文源于翻譯文章 Simple tips for writing clean React components, 原文作者 Iskander Samatov
在這篇文章中,我們會(huì)回顧一些簡(jiǎn)單的技巧,它們將幫助我們編寫(xiě)更簡(jiǎn)潔的 React 組件,并且更好地?cái)U(kuò)展我們的項(xiàng)目。
避免使用擴(kuò)展操作符傳遞 props
首先,讓我們從一個(gè)應(yīng)該避免的反模式開(kāi)始。除非有明確的理由這樣做,否則應(yīng)該避免在組件樹(shù)中使用擴(kuò)展操作符傳遞props,比如:{ ...props }。
通過(guò)這種方式傳遞 props 確實(shí)可以更快的編寫(xiě)組件。但這也使得我們很難去定位代碼中的 bug。會(huì)使我們對(duì)編寫(xiě)的組件失去信心,會(huì)使得我們重構(gòu)組件變得更加困難,而且可能會(huì)導(dǎo)致出現(xiàn)很難排查的 bug。
將函數(shù)參數(shù)封裝成一個(gè)對(duì)象
如果函數(shù)接收多個(gè)參數(shù),最好將它們封裝成一個(gè)對(duì)象。舉個(gè)例子:
export const sampleFunction = ({ param1, param2, param3 }) => {
console.log({ param1, param2, param3 });
}
以這種方式編寫(xiě)函數(shù)簽名有幾個(gè)顯著的優(yōu)點(diǎn):
- 你不用再擔(dān)心參數(shù)傳遞的順序。我曾犯過(guò)幾次因函數(shù)傳參順序問(wèn)題而產(chǎn)生了 bug 的錯(cuò)誤。
- 對(duì)于配置了智能提示的編輯器(現(xiàn)在的大多數(shù)都有),可以很好地完成函數(shù)參數(shù)的自動(dòng)填充。
對(duì)于事件處理函數(shù),將該處理函數(shù)作為函數(shù)的返回值
如果你熟悉函數(shù)式編程,這種編程技術(shù)類似于函數(shù)柯里化,因?yàn)橐呀?jīng)提前設(shè)置了一些參數(shù)。
我們來(lái)看看這個(gè)例子:
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>
)
}
如您所見(jiàn),以這種方式編寫(xiě)處理程序函數(shù),可以使組件樹(shù)保持簡(jiǎn)潔。
組件渲染使用 map 而非 if/else
當(dāng)你需要基于自定義邏輯呈現(xiàn)不同的元素時(shí),我建議使用使用 map 而非 if/else 語(yǔ)句。
下面是一個(gè)使用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>
)
}
下面是一個(gè)使用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>
)
}
使用這個(gè)簡(jiǎn)單的小策略,可以使你的組件變得更具有可讀性,更容易理解。而且它還使邏輯擴(kuò)展變得更簡(jiǎn)單。
Hook組件
只要不濫用,這個(gè)模式是很有用的。
你可能會(huì)發(fā)現(xiàn)自己在應(yīng)用中使用了很多組件。如果它們需要一個(gè)狀態(tài)來(lái)發(fā)揮作用,你可以將他們封裝為一個(gè) hook 提供該狀態(tài)。這些組件的一些好例子是彈出框、toast 通知或簡(jiǎn)單的 modal 對(duì)話框。例如,下面是一個(gè)用于簡(jiǎn)單確認(rèn)對(duì)話框的 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;
以這種方式提取組件可以避免編寫(xiě)大量狀態(tài)管理的樣板代碼。如果你想了解更多 React hooks,請(qǐng)查看 我的帖子。
組件拆分
下面三個(gè)技巧是關(guān)于如何巧妙地拆分組件。根據(jù)我的經(jīng)驗(yàn),保持組件的簡(jiǎn)潔是保持項(xiàng)目可管理的最佳方法。
使用包裝器
如果你正在努力尋找一種方法來(lái)拆分復(fù)雜組件,看看你的組件中每個(gè)元素所提供的功能。有些元素提供了獨(dú)特的功能,比如拖拽功能。
下面是一個(gè)使用react-beautiful-dnd實(shí)現(xiàn)拖拽的組件示例:
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>
)
}
現(xiàn)在,看一下在我們將所有拖拽邏輯移到包裝器之后的組件:
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>
)
}
因此,可以更直觀地看到組件在更高層次上的功能。所有用于拖拽的功能都在包裝器中,使得代碼更容易理解。
關(guān)注點(diǎn)分離
這是我最喜歡的拆分較大組件的方法。
從 React 角度出發(fā),關(guān)注點(diǎn)的分離意味著分離組件中負(fù)責(zé)獲取和改變數(shù)據(jù)的部分和純粹負(fù)責(zé)顯示元素的部分。
這種分離關(guān)注點(diǎn)的方法是引入 hooks 的主要原因。你可以用自定義 hook 封裝所有方法或全局狀態(tài)連接的邏輯。
例如,讓我們看看如下組件:
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>
)
}
下面是它的重構(gòu)版本,使用自定義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 }
}
每個(gè)組件封裝為一個(gè)單獨(dú)的文件
通常大家會(huì)這樣寫(xiě)代碼:
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>
)
}
雖然用這種方式編寫(xiě) React 組件沒(méi)有什么大問(wèn)題,但這并不是一個(gè)好的做法。將 ItemDisplay 組件移動(dòng)到一個(gè)單獨(dú)的文件可以使你的組件松散耦合,易于擴(kuò)展。
在大多數(shù)情況下,要編寫(xiě)干凈整潔的代碼,需要注意并花時(shí)間遵循好的模式和避免反模式。因此,如果你花時(shí)間遵循這些模式,它有助于你編寫(xiě)整潔的 React 組件。我發(fā)現(xiàn)這些模式在我的項(xiàng)目中非常有用,希望你也這么做!
以上就是編寫(xiě)簡(jiǎn)潔React組件的小技巧的詳細(xì)內(nèi)容,更多關(guān)于編寫(xiě)React組件的技巧的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React?中使用?Redux?的?4?種寫(xiě)法小結(jié)
這篇文章主要介紹了在?React?中使用?Redux?的?4?種寫(xiě)法,Redux 一般來(lái)說(shuō)并不是必須的,只有在項(xiàng)目比較復(fù)雜的時(shí)候,比如多個(gè)分散在不同地方的組件使用同一個(gè)狀態(tài),本文就React使用?Redux的相關(guān)知識(shí)給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06
react中實(shí)現(xiàn)搜索結(jié)果中關(guān)鍵詞高亮顯示
這篇文章主要介紹了react中實(shí)現(xiàn)搜索結(jié)果中關(guān)鍵詞高亮顯示,使用react實(shí)現(xiàn)要比js簡(jiǎn)單很多,方法都是大同小異,具體實(shí)現(xiàn)代碼大家跟隨腳本之家小編一起看看吧2018-07-07
React通過(guò)hook實(shí)現(xiàn)封裝表格常用功能
這篇文章主要為大家詳細(xì)介紹了React通過(guò)hook封裝表格常用功能的使用,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2023-12-12
React實(shí)現(xiàn)復(fù)雜搜索表單的展開(kāi)收起功能
本節(jié)對(duì)于需要展開(kāi)收起效果的查詢表單進(jìn)行概述,主要涉及前端樣式知識(shí)。對(duì)React實(shí)現(xiàn)復(fù)雜搜索表單的展開(kāi)-收起功能感興趣的朋友一起看看吧2021-09-09
React項(xiàng)目中使用Redux的?react-redux
這篇文章主要介紹了React項(xiàng)目中使用Redux的?react-redux,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
React中g(shù)etDefaultProps的使用小結(jié)
React中的getDefaultProps功能允許開(kāi)發(fā)者為類組件定義默認(rèn)屬性,提高組件的靈活性和容錯(cuò)性,本文介紹了getDefaultProps的作用、語(yǔ)法以及最佳實(shí)踐,并探討了其他替代方案,如函數(shù)組件中的默認(rèn)參數(shù)、高階組件和ContextAPI等,理解這些概念有助于提升代碼的可維護(hù)性和用戶體驗(yàn)2024-09-09

