Javascript 关于这个定制钩子用法的困惑

Javascript 关于这个定制钩子用法的困惑,javascript,reactjs,react-hooks,Javascript,Reactjs,React Hooks,我正在看一些关于React挂钩的教程,在教程中,作者创建了一个useDropdownhook来呈现可重用的下拉列表。代码是这样的 import React,{useState}来自“React”; const useDropdown=(标签、默认状态、选项)=>{ const[state,updateState]=使用状态(defaultState); const id=`use dropdown-${label.replace(“,”).toLowerCase()}`; 常量下拉列表=()=>

我正在看一些关于React挂钩的教程,在教程中,作者创建了一个
useDropdown
hook来呈现可重用的下拉列表。代码是这样的

import React,{useState}来自“React”;
const useDropdown=(标签、默认状态、选项)=>{
const[state,updateState]=使用状态(defaultState);
const id=`use dropdown-${label.replace(“,”).toLowerCase()}`;
常量下拉列表=()=>(
{label}
updateState(e.target.value)}
onBlur={e=>updateState(e.target.value)}
disabled={!options.length}
>
{options.map(项=>(
{item}
))}
);
返回[状态,下拉列表,更新];
};
导出默认使用下拉列表;
他在这样的组件中使用了这个

import React,{useState,useffect}来自“React”;
从“/useDropdown”导入useDropdown;
常量SomeComponent=()=>{
常量[动物,动物下拉列表]=使用下拉列表(“动物”,“狗”,动物);
const[breed,BreedDropdown,updateBreed]=使用下拉菜单(“breed”,“breed”,“breed”);
返回(
位置
updateLocation(e.target.value)}
/>
提交
);
};
导出默认组件;

他说这样我们可以创建可重用的下拉组件。我想知道这与定义一个普通的旧下拉组件并向其中传递道具有什么不同。在这种情况下,我能想到的唯一区别是,现在我们能够在父组件(即
SomeComponent
)中获取状态和设置状态,并从那里直接读取/设置子组件(即
useDropdown
)的状态。然而,这被认为是一种反模式,因为我们破坏了单向数据流吗?

我同意Drew的观点,即使用自定义钩子仅基于函数参数返回jsx打破了传统的组件抽象。为了扩展这一点,我可以想出在React中使用jsx的四种不同方法

静态JSX

如果jsx不依赖于state/props,您可以将其定义为
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) }
  </>
);
另一个很好的例子是一个钩子,它结合了您实际希望在不同组件/容器中重用(而不是共享)的状态:

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 };
};
这个钩子创建了API调用相关的状态,您可以在处理API调用的任何组件中重用该状态

我得到了一个示例,其中我实际使用了一个自定义钩子来呈现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>
  );
}
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}
    />
  );
};
const useGatsbyImage=(src,alt)=>{
const{data}=useContext(ImagesContext);
常量流体=使用备注(()=>(
data.allFile.nodes.find({relativePath})=>src==relativePath.childImageSharp.fluid
),[数据,src];
返回(
);
};
我可以为它创建一个组件吗?当然,但我也只是抽象出一个上下文,对我来说,这是一个使用钩子的模式。他不是固执己见。您可以定义自己的约定


我想德鲁已经给了你一个很好的答案。我希望我的示例能帮助您更好地了解React提供的不同工具的用法。

虽然对于如何定义自定义钩子以及应该包含什么逻辑没有硬核限制,但编写返回JSX的钩子是一种反模式

您应该评估每种方法给您带来的好处,然后决定一段特定的代码

使用钩子返回JSX有一些缺点

  • 当编写一个返回JSX组件的钩子时,实际上是在功能组件中定义组件,因此每次重新渲染时,都将创建组件的新实例。这将导致卸下并重新安装组件。这对性能不利,如果组件中有状态登录,也会出现问题,因为每次重新呈现父组件时,状态都会被重置
  • 通过在钩子中定义一个JSX组件,您可以在需要时取消延迟加载组件的选项
  • 组件的任何性能优化都要求您使用
    useMemo
    ,这并没有为您提供像React.memo这样的自定义比较器函数的灵活性
另一方面,好处是您可以控制父组件中组件的状态。但是,您仍然可以通过使用受控组件方法实现相同的逻辑

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}来自“React”;
常量下拉列表=创建备忘录((道具)=>{
const{label,value,updateState,options}=props;
const id=`use dropdown-${label.replace(“,”).toLowerCase()}`;
返回(
{label}
updateState(e.target.value)}
onBlur={e=>updateState(e.target.value)}
disabled={!options.length}
>
{options.map(项=>(
{item}
))}
);
});
导出默认下拉列表;
并将其用作

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;
import React,{useState,useffect}来自“React”;
从“/useDropdown”导入useDropdown;
常量SomeComponent=()=>{
常数[动物,更新动物]=使用状态(“狗”);
const[breed,updateBreed]=useState(“”);
返回(
位置
updateLocation(e.target.value)}
/>
提交
);
};
导出默认组件;

反模式是一个直截了当的短语,用来描述其他开发人员不同意的简单或复杂的解决方案。我同意Drew的观点,钩子打破了传统的设计,做的比它应该做的更多

根据,钩子的用途是允许您在不编写clas的情况下使用状态和其他React特性
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;