详解React Hooks是如何工作的

 更新时间:2021年05月15日 09:14:50   作者:lovin  
React Hooks是在React 16.8版本新增的特性,在我看了React官网和一些博客对React Hook的讲解后还是觉得没有get到本质。本篇博客通过手动实现useState()来了解Hook的原理和本质。阅读此篇博客的前提是你要知道一些 React Hooks的基本用法和使用规则,不然会看得云里雾里。

1. React Hooks VS 纯函数

React Hook 说白了就是 React V18.6 新增的一些 API,API的本质就是提供某种功能的函数接口。因此,React Hooks 就是一些函数,但是 React Hooks 不是纯函数。

什么是纯函数呢?就是此函数在相同的输入值时,需产生相同的输出,并且此函数不能影响到外面的数据。
简单理解就是函数里面不能用到在外面定义的变量,因为如果用到了外面定义的变量,当外面的变量改变时会影响函数内部的计算,函数也会影响到外面的变量。

对于 React Hooks 提供的函数 API,恰恰就不是纯函数。
来看一个 useState 的使用语句 const [count, setCount] = useState(0),使用 useState 函数得到的结果并不是全都一样的,因为如果 useState(0) 每次得到的结果都是一样的,那 count 值就永远不会改变了,那 count 所在的页面就永远不会改变,和我们看到的结果就不一样了。由此可知,React Hooks 都不是纯函数,也就是说 Hooks 用到了函数外的变量。

那么是什么特性让 React Hooks 一定不能是纯函数呢?实际上是 React 框架和函数组件本身决定的。我们知道,React 页面渲染的原理就是通过每次 render 得到新的虚拟 DOM ,然后进行 DOM Diff 来渲染页面。而 React 的函数组件是通过执行整个函数得到一个虚拟 DOM。因此在每次页面渲染 render 时,在函数组件内部的所有语句都会重新执行一次。如果在函数组件内部使用的 React Hooks 是纯函数的话,就不会在每次渲染后得到不同的虚拟 DOM 了。

React 规定: 所有 React 组件都必须是纯函数,并禁止修改其自身 props 。

因此在 React V16.8 之前 React Hooks 还没出来的时候,函数组件因为是纯函数,只能返回一个固定的虚拟 DOM,不能包含状态,也不支持生命周期方法。因此,当时仅仅是支持函数组件,但函数组件相比于类组件限制太多,函数组件无法取代类组件,也没类组件好用。

React 希望组件是简单的而不是复杂的,React 认为组件的最佳写法应该是函数,而不是类。因此 React 就新增了 React Hooks,Hook 就是钩子的意思,是 React 提供给函数组件在需要外部功能和数据状态时将其 “钩” 进去,从而完善函数组件,使其能完全代替类组件。

React 的函数组件只能是纯函数,那么每次事件发生时重新 render 函数组件时得到不同的虚拟 DOM 的事就完全交给了 React Hooks,那么 React Hooks 是如何做到的呢?下面就手动实现一个 useState,useState 的具体细节肯定不是这样的,但原理和思路是一样的。

2. 简单 myUseState

React.useState 的第一次执行是将初始值赋予给一个 _state,之后的每次重新 render 时就是读取 _state 的值。[state, setState] 中的 setState 做的事就是改变 _state 的值,然后重新渲染页面。
根据这个原理实现 myUseState 函数如下:

import React from 'react';
import ReactDOM from 'react-dom';

let _state

function myUseState(initialValue){
  if(_state === undefined){
    _state = initialValue
  }
  const setState = (newValue)=>{
    _state = newValue
    render()
  }
  return [_state, setState]
}

function render(){
  ReactDOM.render(<App/>,document.getElementById('root'));
}

function App(){
  const [n, setN] = myUseState(0)
  return (
    <div>
      n: {n}
      <button onClick={() => setN(n+1)}>+1</button>
    </div>
  )
}

ReactDOM.render(<App/>,document.getElementById('root'));

3. 改进 myUseState

上述实现的 myUseState 存在 bug,当在函数组件内用到两次 myUseState 时就会出现问题了,二者共用一个 _state 会出现混乱。
因此需要将上述实现进行改进,改进的思路就是将 _state 定义为一个数据或者是对象,由于我们在函数使用时只传了一个数值,无法确定键值,因此只能使用数据。改进如下:

import React from 'react';
import ReactDOM from 'react-dom';

let _state = []
let index = 0

function myUseState(initialValue){
  const currentIndex = index
  if(_state[currentIndex] === undefined){
    _state[currentIndex] = initialValue
  }
  const setState = (newValue)=>{
    _state[currentIndex] = newValue
    render()
  }
  index++
  return [_state[currentIndex], setState]
}

function render(){
  index = 0
  ReactDOM.render(<App/>,document.getElementById('root'));
}

function App(){
  const [n, setN] = myUseState(0)
  const [m, setM] = myUseState(0)
  return (
    <div>
      n: {n}
      <button onClick={() => setN(n+1)}>+1</button>
      <br/>
      m: {m}
      <button onClick={() => setM(m+1)}>+1</button>
    </div>
  )
}

ReactDOM.render(<App/>,document.getElementById('root'));

4. 实现原理引发的 Hooks 规则

上述实现的 myUseState 肯定不是 React.useState 的具体实现代码,但实现原理是一致的。myUseState 函数封装了函数组件内的数据状态,并对该状态进行管理,以暴露出相关的操作接口的方式提供给函数组件使用。
这样一来,函数组件就和其数据状态分离了,函数组件只负责返回虚拟 DOM 本身就可以了,对于数据状态的管理完全交给其 “钩” 住的 React.useState Hook 就可以了。

从上述的实现思路可以发现,React Hooks 的实现其实是基于 全局变量 和 闭包 原理实现的特殊函数。

但是,正是因为这样的实现方式,限制了 React Hooks 的使用必须是 只在顶层调用Hook,意思就是说 不要在循环,条件或嵌套函数中调用 Hook,如果在 if 条件句中使用了 Hook, 导致组件每次渲染生成时 React.useState 语句的执行次数不对,就会打乱 index 的计数,从而导致数据维护的错误。

上述的实现原理依赖于 index 的正确计数,因此 React 依赖于调用 Hooks 的顺序,

以上就是详解React Hooks是如何工作的的详细内容,更多关于详解React Hooks的资料请关注脚本之家其它相关文章!

相关文章

  • React事件绑定的方式详解

    React事件绑定的方式详解

    react事件绑定时。this并不会指向当前DOM元素。往往使用bind来改变this指向,今天通过本文给大家介绍React事件绑定的方式,感兴趣的朋友
    2021-07-07
  • VSCode配置react开发环境的步骤

    VSCode配置react开发环境的步骤

    本篇文章主要介绍了VSCode配置react开发环境的步骤,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • react native实现监控手势上下拉动效果

    react native实现监控手势上下拉动效果

    这篇文章主要为大家详细介绍了react native实现监控手势上下拉动效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-05-05
  • React中嵌套组件与被嵌套组件的通信过程

    React中嵌套组件与被嵌套组件的通信过程

    这篇文章主要介绍了React中嵌套组件与被嵌套组件的通信过程,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-07-07
  • React-Native实现ListView组件之上拉刷新实例(iOS和Android通用)

    React-Native实现ListView组件之上拉刷新实例(iOS和Android通用

    本篇文章主要介绍了React-Native实现ListView组件之上拉刷新实例(iOS和Android通用),具有一定的参考价值,有兴趣的可以了解一下
    2017-07-07
  • 详解react-refetch的使用小例子

    详解react-refetch的使用小例子

    这篇文章主要介绍了详解react-refetch的使用小例子,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-02-02
  • react 兄弟组件如何调用对方的方法示例

    react 兄弟组件如何调用对方的方法示例

    这篇文章主要介绍了react 兄弟组件如何调用对方的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-10-10
  • React中使用setInterval函数的实例

    React中使用setInterval函数的实例

    这篇文章主要介绍了React中使用setInterval函数的实例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • ios原生和react-native各种交互的示例代码

    ios原生和react-native各种交互的示例代码

    本篇文章主要介绍了ios原生和react-native各种交互的示例代码,非常具有实用价值,需要的朋友可以参考下
    2017-08-08
  • React 组件转 Vue 组件的命令写法

    React 组件转 Vue 组件的命令写法

    本文先介绍两个框架的组件共性和不兼容的地方,再介绍react-to-vue的使用和原理,需要的朋友可以参考下
    2018-02-02

最新评论