如何使用自定义钩子和 useContext 仅呈现相关组件

问题描述

我正在尝试基于 useContext 创建一个自定义钩子 useFocus 以仅将焦点设置在我选择的组件上。 它可以工作,但即使我使用 useCallback 作为我的 useFocus 自定义钩子返回的函数,其他组件也在呈现。

我只想重新渲染焦点不断变化的组件。

我知道如果代码很快,重新渲染可能是小问题,但我不明白为什么要重新渲染。 你能给我一些解释或修复吗。

预期结果

当点击“设置焦点”按钮时,我希望得到:

1 个 A/B/D 渲染

2 个 C/E 渲染

Expected result in image

谢谢。

这是我的代码

import React,{ createContext,useCallback,useContext,useState } from "react";
import "./styles.css";

const StepContext = createContext({});

//This is just to display number or render for each Items
function useRenderCounter() {
  const ref = React.useRef();
  React.useEffect(() => {
    ref.current.textContent = Number(ref.current.textContent || "0") + 1;
  });
  return (
    <span
      style={{
        backgroundColor: "#ccc",borderRadius: 4,padding: "2px 4px",fontSize: "0.8rem",margin: "0 6px",display: "inline-block"
      }}
      ref={ref}
    />
  );
}

const useFocus = (property) => {
  const context = useContext(StepContext);

  const bool = context === property;
  //console.log("bool",bool,context,property);

  //return bool
  return useCallback(() => bool,[bool]);
};

const Item = React.memo(({ property }) => {
  const rendercounter = useRenderCounter();
  const isFocus = useFocus(property);
  //Here I expect to got re-render only for property which the focus changed

  const focus = isFocus();

  console.log(property,"render",focus);

  const style = focus ? { borderStyle: "solid",borderColor: "red" } : {};

  return (
    <div style={{ display: "flex",margin: "4px" }}>
      {rendercounter}
      <div style={style}>{property}</div>
    </div>
  );
});

export default function App() {
  const [focusOn,setFocusOn] = useState("E");

  const handleClick = () => setFocusOn("C");

  return (
    <StepContext.Provider value={focusOn}>
      <div style={{ display: "flex",flexDirection: "column" }}>
        <Item key={1} property={"A"} />
        <Item key={2} property={"B"} />
        <Item key={3} property={"C"} />
        <Item key={4} property={"D"} />
        <Item key={5} property={"E"} />
        <button onClick={handleClick}>set focus</button>
      </div>
    </StepContext.Provider>
  );
}

Here the sandbox

解决方法

当 Provider 获得新值时,无法避免重新渲染。来自official docs on Context API

当 Provider 的 value prop 发生变化时,所有作为 Provider 后代的消费者都会重新渲染。从 Provider 到其后代消费者(包括 .contextType 和 useContext)的传播不受 shouldComponentUpdate 方法的约束,因此即使祖先组件跳过更新,消费者也会更新。