react hooks 背后的 JavaScript 机制是如何工作的?

IT技术 javascript reactjs react-hooks
2021-03-30 05:22:27

我的问题与使react-hooks成为可能的 Javascript 机制有关。

React 最近的开发允许我们创建钩子,即。对于 React 状态,在简单的函数中,如:

function App () {
  const [someVar, setSomeVar] = useState('someVarDefaultValue');
  return (
    <div 
      onClick={() => setSomeVar('newValue')}>{someVar}
    </div>
  );
}

钩子useState返回一个带有访问器和修改器的数组,我们在 App 函数中通过数组分解来使用它们。

所以在引擎盖下,钩子看起来像(只是一个伪代码):

function useState(defaultValue) {
  let value = defaultValue;

  function setValue(val) {
    value = val;
  }

  return [value, setValue];
}

当您在 JS 中尝试这种方法时,它不起作用 - 如果您在setValue某处使用,从数组分解的值将不会更新即使您将value用作对象,而不是原始defaultValue

我的问题是钩子机制在 JS 中是如何工作的?

从我在 React源代码中看到的内容来看,它使用了 reducer 函数和 Flow 类型检查。代码很难让我理解大局。

这个问题不是关于如何在 React 中编写自定义钩子

在这个问题中回答的 React 状态管理上下文中钩子如何在幕后工作也不是问题:React Hooks - What’s under the hood?

1个回答

状态值必须存储在useState函数外部,在组件实例的某些内部表示中,以便它在调用之间返回持久结果。此外,设置该值必须导致在它被调用的组件上重新渲染:

     // useState must have a reference to the component it was called in:
     let context;

     function useState(defaultValue) {
       // Calling useState outside of a component won't work as it needs the context:
       if(!context) throw new Error("Can only be called inside render");
       // Only initialize the context if it wasn't rendered yet (otherwise it would re set the value on a rerender)
       if(!context.value)
        context.value = defaultValue;
       // Memoize the context to be accessed in setValue
       let memoizedContext = context;
       function setValue(val) {
          memoizedContext.value = val;
          // Rerender, so that calling useState will return the new value
          internalRender(memoizedContext);
       }

      return [context.value, setValue];
     }

    // A very simplified React mounting logic:
    function internalRender(component) {
       context = component;
       component.render();
       context = null;
    }

     

     // A very simplified component
     var component = {
      render() {
        const [value, update] = useState("it");
        console.log(value);
        setTimeout(update, 1000, "works!");
      }
    };

    internalRender(component);

然后当setValue被调用时,组件重新渲染,useState将再次被调用,并返回新值。

上面的例子非常简化,这里有一些 React 做的不同的事情:

(1) 状态不是存储在“上下文属性”中,而是存储在链表中。每当useState被调用时,链表前进到下一个节点。这就是为什么你不应该在分支/循环中使用钩子的原因。

(2) setState 函数 get 被缓存,并且每次都返回相同的引用。

(3) 重绘不是同步发生的。

换句话说,关闭救援.. :)
2021-05-25 05:22:27
我的意思是这段代码只是调用它自己的奇特方式!喜欢let component = { value: "default", render() { this.value = "new value"; this.render(); } }
2021-06-05 05:22:27
你怎么知道 defaultValue 存储在 context.value 中?对于 useState 的多次调用,每次调用都会覆盖 context.value...我们还没有在 useState 中明确说明变量的名称,这种情况有什么约定吗?我们如何跟踪哪个 useState 引用哪个存储值?
2021-06-12 05:22:27
@mtx 这只是一个非常简化的演示,旨在易于阅读/理解。实际上要复杂得多
2021-06-13 05:22:27
但是这段代码如何返回新的 Component 呢?因为 update = setValueupdate -> internalRender -> component.render 它基本上是一个递归函数!所以通过调用 update 返回component它自己!
2021-06-17 05:22:27