本文源于翻譯文章 Simple tips for writing clean React components, 原文作者 Iskander Samatov
在這篇文章中,我們會回顧一些簡單的技巧,它們將幫助我們編寫更簡潔的 React 組件,并且更好地擴展我們的項目。
避免使用擴展操作符傳遞 props
首先,讓我們從一個應該避免的反模式開始。除非有明確的理由這樣做,否則應該避免在組件樹中使用擴展操作符傳遞props,比如:{ ...props }。
通過這種方式傳遞 props 確實可以更快的編寫組件。但這也使得我們很難去定位代碼中的 bug。會使我們對編寫的組件失去信心,會使得我們重構組件變得更加困難,而且可能會導致出現很難排查的 bug。
將函數參數封裝成一個對象
如果函數接收多個參數,最好將它們封裝成一個對象。舉個例子:
1
2
3
|
export const sampleFunction = ({ param1, param2, param3 }) => { console.log({ param1, param2, param3 }); } |
以這種方式編寫函數簽名有幾個顯著的優點:
- 你不用再擔心參數傳遞的順序。我曾犯過幾次因函數傳參順序問題而產生了 bug 的錯誤。
- 對于配置了智能提示的編輯器(現在的大多數都有),可以很好地完成函數參數的自動填充。
對于事件處理函數,將該處理函數作為函數的返回值
如果你熟悉函數式編程,這種編程技術類似于函數柯里化,因為已經提前設置了一些參數。
我們來看看這個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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 組件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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 組件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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實現拖拽的組件示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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> ) } |
現在,看一下在我們將所有拖拽邏輯移到包裝器之后的組件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import React from 'react' export default function DraggableSample() { return ( <div> <DragWrapper> {columns.map((column, index) => { return ( <ColumnComponent key={index} column={column}/> ); })} </DragWrapper> </div> ) } |
下面是包裝器的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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 封裝所有方法或全局狀態連接的邏輯。
例如,讓我們看看如下組件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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拆分后的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 本身的代碼:
1
2
3
4
5
6
7
8
9
10
11
|
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 } } |
每個組件封裝為一個單獨的文件
通常大家會這樣寫代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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組件的技巧的資料請關注服務器之家其它相關文章!
原文鏈接:https://juejin.cn/post/6947676213175386126