Select组件|青训营笔记

108 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 7天

我们组选择用react+typescript来构建组件库,我今天先看了下别人的select组件

select组件是外部容器组件,本select组件使用复合组件模式。

Select 组件是维护状态的容器组件。构建选择组件需要两个主要状态。

  • selectedOption(当前选择的选项的值)。
  • showDropdown(显示或隐藏下拉列表的布尔值)。
import React, { ReactNode, useState, useRef } from "react";
import useOnClickOutside from "../../hooks/useOnClickOutside";

const Select: React.FC<{
  children: ReactNode | ReactNode[];
  defaultValue?: string;
  placeholder?: string;
}> = ({ children, defaultValue, placeholder }) => {
  const [selectedOption, setSelectedOption] = useState(defaultValue || "");
  const [showDropdown, setShowDropdown] = useState(false);
  const showDropdownHandler = () => setShowDropdown(!showDropdown);
  const selectPlaceholder = placeholder || "Choose an option";

  const clickOutsideHandler = () => setShowDropdown(false);

 // custom hook to detect the click on the outside
  useOnClickOutside(selectContainerRef, clickOutsideHandler);

  const updateSelectedOption = (option: string) => {
    setSelectedOption(option);
    setShowDropdown(false);
  };

  return (
      <div className="select-container" ref={selectContainerRef}>
        <div
          className={showDropdown ? "selected-text active" : "selected-text"}
          onClick={showDropdownHandler}
        >
          {selectedOption.length > 0 ? selectedOption : selectPlaceholder}
        </div>
        <ul
          className={
            showDropdown
              ? "select-options show-dropdown-options"
              : "select-options hide-dropdown-options"
          }
        >
          {children}
        </ul>
      </div>
  );
};

export default Select;

我正在使用一个名为useOnClickOutside的自定义挂钩。这个钩子就像一个接受 ref 和回调函数的监听器。只要在指定 ref 之外发生点击事件,就会调用回调函数。每当用户在选择组件外部单击时,我在这里使用这个自定义挂钩来隐藏下拉列表。现在我们已经完成构建外部组件(选择)。下一步是构建选项组件。

import React, { ReactNode } from "react";

const Option: React.FC<{
  children: ReactNode | ReactNode[];
  value: string;
}> = ({ children, value }) => {
  return (
    <li className="select-option">
      {children}
    </li>
  );
};

export default Option;

选项组件现已构建。现在是困难的部分,我们需要使用 Option 组件中的 select 组件中的 updateSelectedOption 函数。我们如何在不通过 props 的情况下共享 Select 和 Option 组件之间的功能或状态? 冷静点,React 提供了一种无需通过 props 即可共享数据的方法,这就是 React 上下文发挥作用的地方。如果您不熟悉 React 上下文,请参阅reactjs.org/docs/contex…

Context 提供了一种通过组件树传递数据的方法,而无需在每个级别手动传递 props。

现在让我们为选择组件编写上下文。我们将共享两个值:selectedOption 和 updateSelectedOption。

import { createContext, useContext } from "react";

const SelectContext = createContext<{
  selectedOption: string;
  changeSelectedOption: (option: string) => void;
}>({
  selectedOption: "",
  changeSelectedOption: (option: string) => {}
});

const useSelectContext = () => {
  const context = useContext(SelectContext);
  if (!context) {
    throw new Error("Error in creating the context");
  }
  return context;
};

export { useSelectContext, SelectContext };

我们已经创建了选择上下文,useSelectContext 是使用上下文的自定义挂钩。现在我们需要为上下文提供值。我们可以使用 SelectContext.Provider 元素为上下文提供值。

// Select component
 <SelectContext.Provider
      value={{ selectedOption, changeSelectedOption: updateSelectedOption }}
    >
      <div className="select-container" ref={selectContainerRef}>
       ... 
       ...
      </div>
    </SelectContext.Provider>

现在我们已经为 Context 提供了值。下一步是使用 Option 组件中提供的值。我们可以使用 useSelectContext 从上下文中获取值。

import React, { ReactNode } from "react";
import { useSelectContext } from "./selectContext";

const Option: React.FC<{
  children: ReactNode | ReactNode[];
  value: string;
}> = ({ children, value }) => {
  const { changeSelectedOption } = useSelectContext();

  return (
    <li className="select-option" onClick={() => changeSelectedOption(value)}>
      {children}
    </li>
  );
};

export default Option;

我们现在已经连接了 Option & Select 组件。单击任何选项将触发 changeSelectedOption 函数,该函数将更新 selectedOption 状态。我们还可以在选项组件的上下文中使用 selectedOption 值来突出显示所选选项。

<Select>
     <Option value="one">One</Option>
     <Option value="two">Two</Option>
     <Option value="three">Three</Option>
     <Option value="four">Four</Option>
</Select>