关于这个 React 自定义钩子用法的困惑 [英] confusion about this React custom hook usage

查看:33
本文介绍了关于这个 React 自定义钩子用法的困惑的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在看一些关于 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屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆