一步步实现React-Hooks核心原理

本文详细解析了React Hooks的实现原理,通过逐步实现简易版的useState和useEffect,介绍了闭包、模块模式等关键概念。文章还探讨了如何支持多个Hooks以及Custom Hooks,并重申了Hooks的使用规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React Hooks已经推出一段时间,大家应该比较熟悉,或者多多少少在项目中用过。写这篇文章简单分析一下Hooks的原理,并带大家实现一个简易版的Hooks。

这篇写的比较细,相关的知识点都会解释,给大家刷新一下记忆。

Hooks

Hooks是React 16.8推出的新功能。以这种更简单的方式进行逻辑复用。之前函数组件被认为是无状态的。但是通过Hooks,函数组件也可以有状态,以及类组件的生命周期方法。

useState用法示例:

import React, {
    useState } from 'react';

function Example() {
   
  // count是组件的状态
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {
   count} times</p>
      <button onClick={
   () => setCount(count + 1)}>        Click me      </button>
    </div>
  );
}

闭包

开始之前,我们来简单回顾一下闭包的概念,因为Hooks的实现是高度依赖闭包的。

闭包(Closure),Kyle Simpson在《你不知道的Javascript》中总结闭包是:

Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.

闭包就是,函数可以访问到它所在的词法作用域,即使是在定义以外的位置调用。

闭包的一个重要应用就是,实现内部变量/私有数据。

var counter = 0;

// 给计数器加1
function add() {
   
  counter += 1;
}

// 调用 add() 3次
add(); // 1
add(); // 2
counter = 1000;
add(); // 1003

这里因为counter不是内部变量,所以谁都能修改它的值。我们不想让人随意修改counter怎么办?这时候就可以用闭包:

function getAdd() {
   
  var counter = 0;
  return function add() {
   counter += 1;}
}
var add = getAdd();
add(); // 1
add(); // 2
add(); // 3
counter = 1000 // error! 当前位置无法访问counter

我们还可以把函数的定义挪到调用的位置,用一个立即执行函数表达式IIFE(Immediately Invoked Function Expression):

var add = (function getAdd() {
   
  var counter = 0;
  return function add() {
   counter += 1;}
})();
add(); // 1
add(); // 2
add(); // 3

这种通过IIFE创建闭包的方式也叫做模块模式(Module Pattern),它创建了一个封闭的作用域,只有通过返回的对象/方法来操纵作用域中的值。这个模式由来已久了,之前很多Javascript的库,比如jQuery,就是用它来导出自己的实例的。

开始动手实现

理清闭包的概念后可以着手写了。从简单的入手,先来实现setState。

function useState(initialValue) {
   
  var _val = initialValue; // _val是useState的变量
  function state() {
   
    // state是一个内部函数,是闭包
    return _val;
  }
  function setState(newVal) {
   
    _val = newVal;
  }
  return [state, setState];
}
var [foo, setFoo] = useState(0);
console.log(foo()); // 0
setFoo(1);
console.log(foo()) // 1

根据useState的定义来实现。比较简单不需要多解释。

将useState应用到组件中

现在我们将这个简易版的useState应用到一个Counter组件中:

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

这里简单起见,就不render真实DOM了,因为我们只关心组件的状态,所以每次render的时候打印count的值。参考 前端手写面试题详细解答

这里点击click之后,counter的值加一,useState的基本功能实现了。但现在state是一个函数而不是一个变量,这和React的API不一致,接下来我们就来改正这一点。

过期闭包

function useState(initialValue) {
   
  var _val = initialValue
  // 去掉了state()函数
  function setState(newVal) {
   
    _val = newVal
  }
  return [_val, setState] //直接返回_val
}
var [foo, setFoo] = useState(0)
console.log(foo) // 0
setFoo(1) // 更新_val
console.log(foo) // 0 - BUG!

如果我们直接把state从函数改成变量,问题就出现了,state不更新了。无论点击几次,Counter的值始终不变。这个是过期闭包问题(Stale Closure Problem)。因为在useState返回的时候,state就指向了初始值,所以后面即使counter的值改变了,打印出来的仍然就旧值。我们想要的是,返回一个变量的同时,还能让这个变量和真实状态同步。那如何来实现呢?

模块模式

解决办法就是将闭包放在另一个闭包中。

const MyReact = (function() {
   
  let _val //将_val提升到外层闭包
  return {
   
    render(Component) {
   
      const Comp = Component()
      Comp.render()
      return Comp
    },
    useState(initialValue) {
   
      _val = _val || initialValue //每次刷新
      function setState(newVal) {
   
        _val = newVal
      }
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值