阅读 228

深度剖析React hooks工作原理

在本文中,我们通过构建 React Hooks 来重新引入闭包。这将有两个目的——展示闭包的有效使用,以及展示如何仅用 29 行可读的 JS 来构建 Hooks。最后,我们了解自定义 Hooks 是如何产生的。你不需要为了理解 Hooks 做任何这些。如果你完成这个练习,它可能只会帮助你的 JS 基础知识。别担心,没那么难!

什么是闭包?

闭包是 JS 中的一个基本概念。尽管如此,它们还是因让许多特别是新开发人员感到困惑而臭名昭著。You Don't Know JS 的Kyle Simpson将闭包定义为:

闭包是指函数能够记住并访问其词法范围,即使该函数在其词法范围之外执行。

它们显然与词法作用域的概念密切相关,MDN 将其定义为“当函数嵌套时解析器如何解析变量名”。让我们看一个实际例子来更好地说明这一点:

// Example 0 function useState(initialValue) {   var _val = initialValue   function state() {     return _val   }   function setState(newVal) {     _val = newVal   }   return [state, setState] } var [foo, setFoo] = useState(0) console.log(foo()) setFoo(1) console.log(foo()) 复制代码

在这里,我们正在创建 ReactuseState钩子的原始克隆。在我们的函数中,有 2 个内部函数,statesetState. state返回一个_val上面定义的局部变量并将局部变量setState设置为传递给它的参数(即newVal)。

我们在state这里的实现是一个 getter 函数,它并不理想,但我们稍后会修复它。重要的是,使用fooand setFoo,我们能够访问和操作(又名“关闭”)内部变量_val。它们保留对useState的范围的访问,该引用称为闭包。在 React 和其他框架的上下文中,这看起来像状态,而这正是它的本质。

在函数组件中的使用

让我们useState在熟悉的环境中应用我们新创建的克隆。我们将制作一个Counter组件!

// Example 1 function Counter() {   const [count, setCount] = useState(0)   return {     click: () => setCount(count() + 1),     render: () => console.log('render:', { count: count() })   } } const C = Counter() C.render() C.click() C.render() 复制代码

在这里,我们没有渲染到 DOM,而是选择了console.log退出我们的状态。我们还为我们的 Counter 公开了一个编程 API,因此我们可以在脚本中运行它,而不是附加一个事件处理程序。通过这种设计,我们能够模拟我们的组件渲染和对用户操作的反应。

虽然这有效,但调用 getter 来访问状态并不是真正React.useState钩子的 API 。让我们解决这个问题。

模块中的闭包

我们可以useState通过……将我们的闭包移到另一个闭包中来解决我们的难题!

// Example 2 const MyReact = (function() {   let _val   return {     render(Component) {       const Comp = Component()       Comp.render()       return Comp     },     useState(initialValue) {       _val = _val || initialValue       function setState(newVal) {         _val = newVal       }       return [_val, setState]     }   } })() 复制代码

现在这看起来更像是 React with Hooks!

复制 useEffect

到目前为止,我们已经介绍了useState,这是第一个基本的 React Hook。下一个最重要的钩子是useEffect. 与 不同setStateuseEffect异步执行,这意味着有更多机会遇到闭包问题。

我们可以扩展我们迄今为止建立的 React 的微型模型,以包含以下内容:

// Example 3 const MyReact = (function() {   let _val, _deps   return {     render(Component) {       const Comp = Component()       Comp.render()       return Comp     },     useEffect(callback, depArray) {       const hasNoDeps = !depArray       const hasChangedDeps = _deps ? !depArray.every((el, i) => el === _deps[i]) : true       if (hasNoDeps || hasChangedDeps) {         callback()         _deps = depArray       }     },     useState(initialValue) {       _val = _val || initialValue       function setState(newVal) {         _val = newVal       }       return [_val, setState]     }   } })() // usage function Counter() {   const [count, setCount] = MyReact.useState(0)   MyReact.useEffect(() => {     console.log('effect', count)   }, [count])   return {     click: () => setCount(count + 1),     noop: () => setCount(count),     render: () => console.log('render', { count })   } } let App App = MyReact.render(Counter) // effect 0 // render {count: 0} App.click() App = MyReact.render(Counter) // effect 1 // render {count: 1} App.noop() App = MyReact.render(Counter) // // no effect run // render {count: 1} App.click() App = MyReact.render(Counter) // effect 2 // render {count: 2}  复制代码

为了跟踪依赖关系(因为useEffect依赖关系改变时重新运行),我们引入了另一个变量来跟踪_deps

所以基本的直觉是有一个数组hooks和一个索引,当每个钩子被调用时递增,并在组件呈现时重置。

// Example 4, revisited function Component() {   const [text, setText] = useSplitURL('www.netlify.com')   return {     type: txt => setText(txt),     render: () => console.log({ text })   } } function useSplitURL(str) {   const [text, setText] = MyReact.useState(str)   const masked = text.split('.')   return [masked, setText] } let App App = MyReact.render(Component) // { text: [ 'www', 'netlify', 'com' ] } App.type('www.reactjs.org') App = MyReact.render(Component) // { text: [ 'www', 'reactjs', 'org' ] }}  复制代码

推导出 Hooks 的规则

请注意,从这里您可以轻松理解Hooks 规则的第一条:仅在顶层调用 Hooks。我们已经用我们的currentHook变量明确地模拟了 React 对调用顺序的依赖。您可以在考虑到我们的实施的情况下通读规则的全部解释,并完全理解正在发生的一切。

另请注意,第二条规则“仅从 React 函数调用钩子”也不是我们实现的必要结果,但明确划分代码的哪些部分依赖于有状态逻辑当然是一个好习惯。(作为一个很好的副作用,它还可以更轻松地编写工具以确保您遵循第一条规则。通过在循环和条件内包装命名为常规 JavaScript 函数的有状态函数。以下规则 2 可帮助您遵循规则 1。)


作者:so丶简单
链接:https://juejin.cn/post/7031074478977187853


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐