关于这个 React 自定义钩子用法的困惑 [英] confusion about this React custom hook usage
问题描述
我正在看一些关于 React Hooks 的教程,在教程中作者创建了一个 useDropdown
钩子来呈现可重用的下拉列表.代码是这样的
import React, { useState } from "react";const useDropdown = (label, defaultState, options) =>{const [state, updateState] = useState(defaultState);const id = `use-dropdown-${label.replace(" ", "").toLowerCase()}`;const 下拉 = () =>(<label htmlFor={id}>{标签}<选择id={id}值={状态}onChange={e =>更新状态(e.target.value)}onBlur={e =>更新状态(e.target.value)}禁用={!options.length}><选项/>{options.map(item => (<option key={item} value={item}>{物品}</选项>))}</选择>标签>);返回 [状态,下拉菜单,更新状态];};导出默认 useDropdown;
他在这样的组件中使用了这个
import React, { useState, useEffect } from "react";从./useDropdown"导入 useDropdown;const SomeComponent = () =>{const [animal, AnimalDropdown] = useDropdown("Animal", "dog", ANIMALS);const [breed, BreedDropdown, updateBreed] = useDropdown("Breed", "",breeds);返回 (<div className="search-params"><表格><label htmlFor="位置">地点<输入id="位置"值={位置}占位符=位置"onChange={e =>更新位置(e.target.value)}/>标签><AnimalDropdown/><品种下拉/><按钮>提交按钮></表单>
);};导出默认 SomeComponent;
他说这样我们就可以创建可重用的下拉组件.我想知道这与定义一个普通的旧 Dropdown 组件并将道具传递给它有什么不同.在这种情况下,我能想到的唯一区别是,现在我们可以获取父组件(即 SomeComponent
)中的 state 和 setState 并读取/设置子组件的状态(即useDropdown
) 直接从那里输出的组件.然而,这是否被认为是一种反模式,因为我们正在打破单向数据流?
虽然对于如何定义自定义钩子以及应该包含什么逻辑没有硬核限制,但它是一种编写返回 JSX 的钩子的反模式
你应该评估每种方法给你带来的好处,然后决定一段特定的代码
使用钩子返回 JSX 有一些缺点
- 当您编写一个返回 JSX 组件的钩子时,您实际上是在功能组件中定义了该组件,因此在每次重新渲染时,您都将创建该组件的一个新实例.这将导致组件被卸载并再次安装.如果您在组件内有状态登录,这对性能不利,也会有问题,因为每次重新渲染父级都会重置状态
- 通过在钩子中定义 JSX 组件,您可以在需要时取消延迟加载组件的选项.
- 对组件进行任何性能优化都需要您使用
useMemo
,而它不会为您提供像 React.memo 这样的自定义比较器函数的灵活性
另一方面的好处是您可以控制父组件中的状态.但是,您仍然可以使用受控组件方法来实现相同的逻辑
import React, { useState } from "react";const Dropdown = Reat.memo((props) => {const { 标签、值、更新状态、选项 } = 道具;const id = `use-dropdown-${label.replace(" ", "").toLowerCase()}`;返回 (<label htmlFor={id}>{标签}<选择id={id}价值={价值}onChange={e =>更新状态(e.target.value)}onBlur={e =>更新状态(e.target.value)}禁用={!options.length}><选项/>{options.map(item => (<option key={item} value={item}>{物品}</选项>))}</选择>标签>);});导出默认下拉菜单;
并将其用作
import React, { useState, useEffect } from "react";从./useDropdown"导入 useDropdown;const SomeComponent = () =>{const [animal, updateAnimal] = useState("dog");const [品种,更新品种] = useState("");返回 (<div className="search-params"><表格><label htmlFor="位置">地点<输入id="位置"值={位置}占位符=位置"onChange={e =>更新位置(e.target.value)}/>标签><Dropdown label="animal" value={animal} updateState={updateAnimal} options={ANIMALS}/><Dropdown label="breed" value={breed} updateState={updateBreed} options={breeds}/><按钮>提交按钮></表单>
);};导出默认 SomeComponent;
I was looking at some tutorial on React Hooks and in the tutorial the author created a useDropdown
hook for rendering reusable dropdowns. The code is like this
import React, { useState } from "react";
const useDropdown = (label, defaultState, options) => {
const [state, updateState] = useState(defaultState);
const id = `use-dropdown-${label.replace(" ", "").toLowerCase()}`;
const Dropdown = () => (
<label htmlFor={id}>
{label}
<select
id={id}
value={state}
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>
);
return [state, Dropdown, updateState];
};
export default useDropdown;
and he used this in a component like this
import React, { useState, useEffect } from "react";
import useDropdown from "./useDropdown";
const SomeComponent = () => {
const [animal, AnimalDropdown] = useDropdown("Animal", "dog", ANIMALS);
const [breed, BreedDropdown, updateBreed] = useDropdown("Breed", "", breeds);
return (
<div className="search-params">
<form>
<label htmlFor="location">
Location
<input
id="location"
value={location}
placeholder="Location"
onChange={e => updateLocation(e.target.value)}
/>
</label>
<AnimalDropdown />
<BreedDropdown />
<button>Submit</button>
</form>
</div>
);
};
export default SomeComponent;
He said this way we can create reusable dropdown components. I was wondering how is this different from defining a plain old Dropdown component and pass props into it. The only difference I can think of in this case is that now we have the ability to get the state and setState in the parent component(i.e. SomeComponent
) and read / set the state of the child(i.e. the component output by useDropdown
) directly from there. However is this considered an anti-pattern since we are breaking the one way data flow?
While there is no hard core restriction on how you should define custom hooks and what logic should the contain, its an anti-pattern to write hooks that return JSX
You should evaluate what benefits each approach gives you and then decide on a particular piece of code
There are a few downsides to using hooks to return JSX
- When you write a hook that returns JSX component, you are essentially defining the component within the functional component, so on each and every re-render you will be creating a new instance of the component. This will lead to the component being unmounted and mounted again. Which is bad for performance and also buggy if you have stateful login within the component as the state will get reset with every re-render of parent
- By defining a JSX component within the hook, you are taking away the option of lazy loading your component if the need be.
- Any performance optimization to the component will require you to make use of
useMemo
which doesn't give you the flexibility of a custom comparator function like React.memo
The benefit on the other hand is that you have control over the state of the component in the parent. However you can still implement the same logic by using a controlled component approach
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;
and use it as
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;
这篇关于关于这个 React 自定义钩子用法的困惑的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!