对 usEeffect 和 useState 的函数组件问题做出反应

react function component issue with usEffect and useState

有时我必须使用一些原生的js libaray api,所以我可能有这样一个组件:

function App() {
  const [state, setState] = useState(0)
  useEffect(() => {
    const container = document.querySelector('#container')
    const h1 = document.createElement('h1')
    h1.innerHTML = 'h1h1h1h1h1h1'
    container.append(h1)
    h1.onclick = () => {
      console.log(state)
    }
  }, [])
  return (
    <div>
      <button onClick={() => setState(state => state + 1)}>{state}</button>
      <div id="container"></div>
    </div>
  )
}

以上是一个简单的例子。我应该在安装反应后初始化库,并绑定一些事件处理程序。问题来了:如上所示,如果我使用 useEffect() 而没有 state 作为 dependencies 数组中的项目,则 onclick 处理程序中的值 state 可能永远不会改变。但是如果我将 state 添加到 dependencies 数组,效果函数将在每次状态更改时执行。上面是一个简单的例子,但是真正的库的初始化可能会非常昂贵,所以这种方式是不行的。

现在我找到了 3 种方法来解决这个问题,但其中 none 令我满意。

  1. 创建一个 ref 来保持状态,并添加一个效果来改变它 current 每次 state 改变。 (一个额外的变量和效果)
  2. 与第一个一样,但在函数外定义一个变量而不是 ref。 (部分为首)
  3. 使用class组件。 (这个太多了)

那么有没有解决问题并使代码更好的解决方案?

我认为您已经很好地总结了这些选项。我只想添加一个选项,即您可以将代码拆分为一个初始化效果和一个仅更改 onclick 的效果。初始化逻辑可以 运行 一次,onclick 可以 运行 每次渲染:

const [state, setState] = useState(0)
const h1Ref = useRef();
useEffect(() => {
  const container = document.querySelector('#container')
  const h1 = document.createElement('h1')
  h1Ref.current = h1;
  // Do expensive initialization logic here
}, [])
useEffect(() => {
  // If you don't want to use a ref, you could also have the second effect query the dom to find the h1
  h1ref.current.onClick = () => {
    console.log(state);
  }
}, [state]);

此外,您可以稍微简化选项 #1。您无需创建 useEffect 来更改 ref.current,您只需在组件主体中执行此操作即可:

const [state, setState] = useState(0);
const ref = useRef();
ref.current = state;
useEffect(() => {
  const container = document.querySelector('#container');
  // ...
  h1.onClick = () => {
    console.log(ref.current);
  }
}, []);