3 回答
TA貢獻(xiàn)1810條經(jīng)驗(yàn) 獲得超4個贊
雖然對于如何定義自定義鉤子以及應(yīng)該包含什么邏輯沒有硬核限制,但它是一種反模式,可以編寫返回JSX的鉤子。
您應(yīng)該評估每種方法給您帶來的好處,然后決定特定的代碼段
使用鉤子返回JSX有一些缺點(diǎn)
當(dāng)您編寫返回 JSX 組件的鉤子時,您實(shí)際上是在功能組件中定義組件,因此每次重新渲染時,您都將創(chuàng)建該組件的新實(shí)例。這將導(dǎo)致組件被卸載并再次安裝。這對性能不利,如果您在組件中有狀態(tài)登錄,也會有缺陷,因?yàn)闋顟B(tài)將隨著父級的每次重新渲染而重置
通過在鉤子中定義 JSX 組件,您可以根據(jù)需要取消延遲加載組件的選項(xiàng)。
對組件的任何性能優(yōu)化都需要您使用,而這些優(yōu)化并不能為您提供自定義比較器功能(如 React.memo)的靈活性
useMemo
另一方面的好處是,您可以控制父級組件的狀態(tài)。但是,您仍然可以通過使用受控組件方法實(shí)現(xiàn)相同的邏輯
import React, { useState } from "react";
const Dropdown = Reat.memo((props) => {
const { label, value, updateState, options } = props;
const id = `use-dropdown-${label.replace(" ", "").toLowerCase()}`;
return (
<label htmlFor={id}>
{label}
<select
id={id}
value={value}
onChange={e => updateState(e.target.value)}
onBlur={e => updateState(e.target.value)}
disabled={!options.length}
>
<option />
{options.map(item => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
</label>
);
});
export default Dropdown;
并將其用作
import React, { useState, useEffect } from "react";
import useDropdown from "./useDropdown";
const SomeComponent = () => {
const [animal, updateAnimal] = useState("dog");
const [breed, updateBreed] = useState("");
return (
<div className="search-params">
<form>
<label htmlFor="location">
Location
<input
id="location"
value={location}
placeholder="Location"
onChange={e => updateLocation(e.target.value)}
/>
</label>
<Dropdown label="animal" value={animal} updateState={updateAnimal} options={ANIMALS}/>
<Dropdown label="breed" value={breed} updateState={updateBreed} options={breeds}/>
<button>Submit</button>
</form>
</div>
);
};
export default SomeComponent;
TA貢獻(xiàn)1816條經(jīng)驗(yàn) 獲得超6個贊
反模式是一個直言不諱的短語,用于描述其他開發(fā)人員不同意的簡單或復(fù)雜的解決方案。我同意德魯?shù)挠^點(diǎn),即鉤子打破了傳統(tǒng)的設(shè)計(jì),做了比它應(yīng)該做的更多的事情。
根據(jù) React 的鉤子文檔,鉤子的目的是允許你使用狀態(tài)和其他 React 特性,而無需編寫類。這通常被認(rèn)為是設(shè)置狀態(tài),執(zhí)行計(jì)算任務(wù),在異步事務(wù)中執(zhí)行API或其他查詢,以及響應(yīng)用戶輸入。理想情況下,功能組件應(yīng)該可以與類組件互換,但實(shí)際上,這要困難得多。
用于創(chuàng)建下拉列表組件的特定解決方案雖然有效,但并不是一個好的解決方案。為什么?這是令人困惑的,它不是不言自明的,很難理解發(fā)生了什么。使用鉤子,它們應(yīng)該很簡單并執(zhí)行單個任務(wù),例如按鈕回調(diào)處理程序,計(jì)算并返回記憶結(jié)果,或執(zhí)行通常委派給的其他一些任務(wù)。this.doSomething()
返回 JSX 的鉤子根本不是真正的鉤子,它們只是功能組件,即使它們對鉤子使用正確的前綴命名約定。
在 React 和組件更新的單向通信方面也存在混亂。對數(shù)據(jù)可以通過哪種方式?jīng)]有限制,并且可以以與Angular類似的方式進(jìn)行處理。有一些庫允許您訂閱和發(fā)布對共享類屬性的更改,這些庫將更新偵聽的任何 UI 組件,并且該組件也可以更新它。您還可以使用 RxJS 隨時進(jìn)行異步更改,從而更新 UI。mobx
具體示例確實(shí)避開了 SOLID 原則,為父組件提供輸入點(diǎn)來控制子組件的數(shù)據(jù)。這是典型的強(qiáng)類型語言,如Java,其中進(jìn)行異步通信更加困難(現(xiàn)在不是真正的問題,但曾經(jīng)是)。父組件沒有理由不能更新子組件 - 這是 React 的基本組成部分。添加的抽象越多,添加的復(fù)雜性就越高,故障點(diǎn)就越多。
添加異步函數(shù)、可觀察量 (mobx/rxjs) 或上下文的使用可以減少直接數(shù)據(jù)耦合,但它將創(chuàng)建更復(fù)雜的解決方案。
TA貢獻(xiàn)1780條經(jīng)驗(yàn) 獲得超5個贊
我同意 Drew 的觀點(diǎn),即使用自定義鉤子僅返回基于函數(shù)參數(shù)的 jsx 會破壞傳統(tǒng)的組件抽象。為了擴(kuò)展這一點(diǎn),我可以想出四種不同的方法來使用 React 中的 jsx。
靜態(tài) JSX
如果jsx不依賴于狀態(tài)/道具,你可以把它定義為一個甚至在你的組件之外。如果您有一系列內(nèi)容,這尤其有用。const
例:
const myPs =
[
<p key="who">My name is...</p>,
<p key="what">I am currently working as a...</p>,
<p key="where">I moved to ...</p>,
];
const Component = () => (
<>
{ myPs.map(p => p) }
</>
);
元件
對于 jsx 的有狀態(tài)和無狀態(tài)部分。組件是將 UI 分解為可維護(hù)和可重用部分的 React 方式。
上下文
上下文提供程序返回 jsx(因?yàn)樗鼈円彩恰爸皇恰苯M件)。通常,您只需將子組件包裝在要提供的上下文中,如下所示:
return (
<UserContext.Provider value={context}>
{children}
</UserContext.Provider>
);
但上下文也可用于開發(fā)全局組件。想象一個維護(hù)全局模式對話的對話上下文。目標(biāo)是永遠(yuǎn)不要一次打開多個模式對話框。您可以使用上下文來管理對話框的狀態(tài),但也可以通過上下文提供程序組件呈現(xiàn)全局對話框 jsx:
function DialogProvider({ children }) {
const [showDialog, setShowDialog] = useState(false);
const [dialog, setDialog] = useState(null);
const openDialog = useCallback((newDialog) => {
setDialog(newDialog);
setShowDialog(true);
}, []);
const closeDialog = useCallback(() => {
setShowDialog(false);
setDialog(null);
}, []);
const context = {
isOpen: showDialog,
openDialog,
closeDialog,
};
return (
<DialogContext.Provider value={context}>
{ showDialog && <Dialog>{dialog}</Dialog> }
{children}
</DialogContext.Provider>
);
}
更新上下文還將為用戶更新 UI 中的全局對話框。設(shè)置新對話框?qū)h除舊對話框。
定制掛鉤
通常,鉤子是封裝要在組件之間共享的邏輯的好方法。我看到它們被用作復(fù)雜上下文的抽象層。想象一下,一個非常復(fù)雜,你的大多數(shù)組件只關(guān)心用戶是否登錄,你可以通過自定義鉤子將其抽象出來。UserContextuseIsLoggedIn
const useIsLoggedIn = () => {
const { user } = useContext(UserContext);
const [isLoggedIn, setIsLoggedIn] = useState(!!user);
useEffect(() => {
setIsLoggedIn(!!user);
}, [user]);
return isLoggedIn;
};
另一個很好的例子是一個鉤子,它結(jié)合了你實(shí)際上想要在不同組件/容器中重用(而不是共享)的狀態(tài):
const useStatus = () => {
const [status, setStatus] = useState(LOADING_STATUS.IS_IDLE);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(status === LOADING_STATUS.IS_LOADING);
}, [status]);
return { status, setStatus, isLoading };
};
此掛鉤創(chuàng)建 API 調(diào)用相關(guān)狀態(tài),您可以在處理 API 調(diào)用的任何組件中重用該狀態(tài)。
我舉了一個例子,我實(shí)際上使用自定義鉤子來渲染jsx,而不是使用組件:
const useGatsbyImage = (src, alt) => {
const { data } = useContext(ImagesContext);
const fluid = useMemo(() => (
data.allFile.nodes.find(({ relativePath }) => src === relativePath).childImageSharp.fluid
), [data, src]);
return (
<Img
fluid={fluid}
alt={alt}
/>
);
};
我可以為此創(chuàng)建一個組件嗎?當(dāng)然,但我也只是抽象出一個上下文,對我來說,這是一個使用鉤子的模式。反應(yīng)不是固執(zhí)己見的。您可以定義自己的約定。
再一次,我認(rèn)為德魯已經(jīng)給了你一個很好的答案。我希望我的例子能幫助你更好地了解 React 為你提供的不同工具的用法。
添加回答
舉報(bào)
