React 開發人員可以透過使用設計模式來節省時間和精力,設計模式提供了一種使用經過測試且可信賴的解決方案來解決問題的快速方法。它們支援低耦合的內聚模組,從而幫助 React 開發人員建立可維護、可擴展且高效的應用程式。在本文中,我們將探索 React 設計模式並研究它們如何改進 React 應用程式的開發。
容器和表示模式是一種旨在將反應程式碼中的表示邏輯與業務邏輯分離的模式,從而使其模組化、可測試並遵循關注點分離原則。
大多數情況下,在 React 應用程式中,我們需要從後端/儲存取得資料或計算邏輯並在 React 元件上表示該計算的結果。在這些情況下,容器和表示模式大放異彩,因為它可用於將元件分為兩類,即:
容器元件,充當負責資料取得或計算的元件。
表示元件,其工作是將獲取的資料或計算值呈現在 UI(使用者介面)上。
容器和表示模式的範例如下所示:
import React, { useEffect } from 'react';
import CharacterList from './CharacterList';
const StarWarsCharactersContainer:React.FC = () => {
const [characters, setCharacters] = useState<Character>([])
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
const getCharacters = async () => {
setIsLoading(true);
try {
const response = await fetch("https://akabab.github.io/starwars-api/api/all.json");
const data = await response.json();
setIsLoading(false);
if (!data) return;
setCharacters(data);
} catch(err) {
setError(true);
} finally {
setIsLoading(true);
}
};
useEffect(() => {
getCharacters();
}, []);
return <CharacterList loading={loading} error={error} characters={characters} />;
};
export default StarWarsCharactersContainer;
// the component is responsible for displaying the characters
import React from 'react';
import { Character } from './types';
interface CharacterListProps {
loading: boolean;
error: boolean;
users: Character[];
}
const CharacterList: React.FC<CharacterListProps> = ({ loading, error, characters }) => {
if (loading && !error) return <div>Loading...</div>;
if (!loading && error) return <div>error occured.unable to load characters</div>;
if (!characters) return null;
return (
<ul>
{characters.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
export default CharacterList;
Hooks 是 React 16.8 中首次推出的全新功能。從那時起,他們在開發 React 應用程式中發揮了至關重要的作用。掛鉤是基本函數,可授予功能元件存取狀態和生命週期方法(以前僅可用於類別元件)的功能。另一方面,掛鉤可以專門設計來滿足元件要求並具有其他用例。
我們現在可以隔離所有狀態邏輯(一種需要反應性狀態變數的邏輯),並使用自訂掛鉤在元件中組合或使用它。因此,程式碼更加模組化和可測試,因為鉤子鬆散地綁定到元件,因此可以單獨測試。
帶有鉤子的元件組合示例如下所示:
// creating a custom hook that fetches star wars characters
export const useFetchStarWarsCharacters = () => {
const [characters, setCharacters] = useState<Character>([])
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(false);
const controller = new AbortController()
const getCharacters = async () => {
setIsLoading(true);
try {
const response = await fetch(
"https://akabab.github.io/starwars-api/api/all.json",
{
method: "GET",
credentials: "include",
mode: "cors",
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
signal: controller.signal
}
);
const data = await response.json();
setIsLoading(false);
if (!data) return;
setCharacters(data);
} catch(err) {
setError(true);
} finally {
setIsLoading(true);
}
};
useEffect(() => {
getCharacters();
return () => {
controller.abort();
}
}, []);
return [
characters,
isLoading,
error
];
};
建立自訂鉤子後,我們將其導入到我們的 StarWarsCharactersContainer 元件中並使用它;
// importing the custom hook to a component and fetch the characters
import React from 'react';
import { Character } from './types';
import { useFetchStarWarsCharacters } from './useFetchStarWarsCharacters';
const StarWarsCharactersContainer:React.FC = () => {
const [ characters, isLoading, error ] = useFetchStarWarsCharacters();
return <CharacterList loading={loading} error={error} characters={characters} />;
};
export default StarWarsCharactersContainer;
<橫幅隨機/>
大多數情況下,處理元件中的許多狀態會導致許多未分組狀態的問題,這可能是處理起來很麻煩且具有挑戰性的。在這種情況下,減速器模式可能是有用的選擇。我們可以使用減速器將狀態分類為某些操作,這些操作在執行時可以變更分組的狀態。
此模式允許使用它的開發人員控制元件和/或掛鉤的狀態管理,讓他們在發送事件時管理狀態變更。
使用減速器模式的範例如下所示:
在上面的程式碼中,元件調度兩個操作:
*“註銷”操作只是將狀態重設為其初始值。
提供者模式對於資料管理非常有用,因為它利用上下文 API 透過應用程式的元件樹傳遞資料。這種模式是一種有效的解決支柱鑽井問題的方法,這一直是 React 開發中普遍關注的問題。
為了實現提供者模式,我們首先建立一個提供者元件。 Provider 是 Context 物件提供給我們的一個高階元件。我們可以利用React提供的createContext方法來建構一個Context物件。
export const ThemeContext = React.createContext(null);
export function ThemeProvider({ children }) {
const [theme, setTheme] = React.useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
建立提供者後,我們將使用建立的提供者元件封裝依賴上下文 API 中的資料的元件。
為了從上下文 API 取得資料,我們呼叫 useContext 鉤子,它接受上下文作為參數(在本例中為 ThemeContext)。
import { useContext } from 'react';
import { ThemeProvider, ThemeContext } from "../context";
const HeaderSection = () => {
<ThemeProvider>
<TopNav />
</ThemeProvider>;
};
const TopNav = () => {
const { theme, setTheme } = useContext(ThemeContext);
return (
<div style={{ backgroundColor: theme === "light" ? "#fff" : "#000 " }}>
...
</div>
);
};
高階元件接受一個元件作為參數,並傳回一個注入了附加資料或功能的增壓元件。 React 中 HOC 的可能性是由於 React 更喜歡組合而不是繼承。
高階元件 (HOC) 模式提供了一種增加或修改元件功能的機制,促進元件重複使用和程式碼共用。
HOC 模式的範例如下所示:
import React from 'react'
const higherOrderComponent = Component => {
return class HOC extends React.Component {
state = {
name: 'John Doe'
}
render() {
return <Component name={this.state.name {...this.props} />
}
}
const AvatarComponent = (props) => {
return (
<div class="flex items-center justify-between">
<div class="rounded-full bg-red p-4">
{props.name}
</div>
<div>
<p>I am a {props.description}.</p>
</div>
</div>
)
}
const SampleHOC = higherOrderComponent(AvatarComponent);
const App = () => {
return (
<div>
<SampleHOC description="Frontend Engineer" />
</div>
)
}
export default App;
在上面的程式碼中, <AvatarComponent/> 由 higherOrderComponent 提供 props,它將在內部使用。
複合元件模式是一種 React 設計模式,用於管理由子元件組成的父元件。
這種模式背後的原理是將父元件分解為更小的元件,然後使用 props、上下文或其他反應資料管理技術來管理這些較小元件之間的互動。
當需要建立由較小元件組成的可重複使用、多功能元件時,這種模式會派上用場。它使開發人員能夠建立複雜的 UI 元件,這些元件可以輕鬆自訂和擴展,同時保持清晰簡單的程式碼結構。
複合元件模式的用例範例如下所示:
import React, { createContext, useState } from 'react';
const ToggleContext = createContext();
function Toggle({ children }) {
const [on, setOn] = useState(false);
const toggle = () => setOn(!on);
return (
<ToggleContext.Provider value={{ on, toggle }}>
{children}
</ToggleContext.Provider>
);
}
Toggle.On = function ToggleOn({ children }) {
const { on } = useContext(ToggleContext);
return on ? children : null;
}
Toggle.Off = function ToggleOff({ children }) {
const { on } = useContext(ToggleContext);
return on ? null : children;
}
Toggle.Button = function ToggleButton(props) {
const { on, toggle } = useContext(ToggleContext);
return <button onClick={toggle} {...props} />;
}
function App() {
return (
<Toggle>
<Toggle.On>The button is on</Toggle.On>
<Toggle.Off>The button is off</Toggle.Off>
<Toggle.Button>Toggle</Toggle.Button>
</Toggle>
);
}
這需要從幾個相關的 props 建立一個物件,並將其作為單個 props 傳遞給元件。
這種模式允許我們清理程式碼並使管理 props 變得更簡單,當我們想要將大量相關屬性傳遞給元件時,它特別有用。
import React from 'react';
function P(props) {
const { color, size, children, ...rest } = props;
return (
<p style={{ color, fontSize: size }} {...rest}>
{ children }
</p>
);
}
function App() {
const paragraphProps = {
color: "red",
size: "20px",
lineHeight: "22px"
};
return <P {...paragraphProps}>This is a P</P>;
}
受控輸入模式可用於處理輸入欄位。此模式涉及使用事件處理程序在輸入欄位的值發生變更時更新元件狀態,以及將輸入欄位的目前值儲存在元件狀態中。
由於React 控制元件的狀態和行為,因此該模式使程式碼比不受控制的輸入模式更具可預測性和可讀性,後者不使用元件的狀態,而是直接透過DOM(文件物件模型)對其進行控制。
受控輸入模式的用例範例如下所示:
import React, { useState } from 'react';
function ControlledInput() {
const [inputValue, setInputValue] = useState('');
const handleChange = (event) => {
setInputValue(event.target.value);
};
return (
<input type="text" value={inputValue} onChange={handleChange} />
);
}
稱為 ForwardRef 的高階元件將另一個元件作為輸入並輸出一個傳遞原始元件引用的新元件。透過這樣做,子元件的 ref(可用於檢索底層 DOM 節點或元件實例)可供父元件存取。
當建立與第三方程式庫或應用程式中的另一個自訂元件互動的自訂元件時,在工作流程中包含 ForwardRef 模式非常有幫助。透過授予對庫的 DOM 節點或另一個元件的 DOM 實例的存取權限,它有助於將此類元件的控制權轉移給您。
forwardRef 模式的用例範例如下所示:
import React from "react";
const CustomInput = React.forwardRef((props, ref) => (
<input type="text" {...props} ref={ref} />
));
const ParentComponent = () => {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <CustomInput ref={inputRef} />;
};
在上面的程式碼中,我們使用「forwardRefs」從元件「<ParentComponent/>」觸發了另一個元件「<CustomInput/>」的焦點。
我們在本文中討論了 React 設計模式,包括高階元件、容器呈現元件模式、複合元件、受控元件等等。透過將這些設計模式和最佳實踐合併到您的 React 專案中,您可以提高程式碼質量,促進團隊協作,並使您的應用程式更具可擴展性、靈活性和可維護性。