React hooks是什么

React hooks可以让我们在不需要编写class组件的情况下来实现class组件有的功能,例如组件状态,组件的生命周期等。

React hooks解决了什么问题

  • 在组件之间复用状态逻辑很难

React hooks出现之前,我们在复用组件状态,可以实现的方案有 render Props 和 高阶组件,我们需要大量的代码来实现它,还有高阶组件也会导致组件嵌套过多的问题。使用hooks就可以提取状态逻辑,显得更加简洁

  • 复杂组件变得难以理解

随着功能的迭代,class组件内部持续新增功能代码,例如在生命周期 componentDidMount 和 componentDidUpdate 钩子需要请求多个接口获取数据,绑定多个事件等操作。之后需要在 componentWillUnmount 移除绑定的事件等操作。
虽然可以通过组件粒度来控制代码的复杂度,但是又会导致整个组件的状态逻辑管理更加复杂,虽然可以用redux来解决,但是细粒度的组件,开发还是需要在不同的组件文件之间切换,也是非常麻烦

useState是如何状态隔离的

这里涉及到一个概念,闭包

闭包是什么?

闭包在MDN的解释如下

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。闭包让你可以在一个内层函数中访问到其外层函数的作用域

简单的说,利用闭包可以让函数做到私有变量成为可能

useState的简单实现

我们尝试通过闭包来实现一个简单的useState

function useState(initVal) {
  let _val = initVal; // _val是useState的内部变量
  const state = () => _val; // 通过函数来访问内部变量
  const setState = (newVal) => {
    _val = newVal;
  }
  return [state, setState];
}
const [foo, setFoo] = useState(1); // 数组结构
console.log(foo()); // 1
setFoo(2);
console.log(foo()); // 2

上面的useState使用了闭包维护私有变量 _val,通过 foo 函数来访问 _val,通过 setFoo 来修改 _val,但是跟react的 useState 不同,我们需要通过 foo 函数来获取,如何通过 foo 来直接访问呢,聪明的你可能会这样写

function useState(initVal) {
  let _val = initVal; // _val是useState的内部变量
  const setState = (newVal) => {
    _val = newVal;
  }
  return [_val, setState];
}

然而是错的,useState里面结构出来的 foo ,引用的 _val 是初始的值(问题1),所以我们第二次打印出来的结果,还是1

const [foo, setFoo] = useState(1);
console.log(foo); // 1
setFoo(2);
console.log(foo); // 1

那么如何解决呢?我们需要先了解模块化模式

模块化模式

在JavaScript中,模块模式被用来进一步模拟类的概念,这样我们就可以在单个对象中包含公共/私有方法和变量,从而将特定部分屏蔽在全局作用域之外。这样做的结果是减少了函数名与页面上其他脚本中定义的其他函数冲突的可能性。

我们通过模块React来封装上面的useState

const React = (function() {
  let _val; // _val变量提升到React模块
  
  function useState(initVal) {
    const state = _val || initVal;
    const setState = newVal => {
      _val = newVal;
    }
    return [state, setState];
  }

  function render(Component) {
    const C = new Component();
    C.render();
    return C;
  }

  return {
    useState,
    render,
  }
})();

我们定义一个React模块,然后暴露 useStaterender 方法,这里的 render 每次调用,都会重新调用 useStatecount 也会拿到最新的值,解决了上面的(问题1

function Component() {
  const [count, setCount] = React.useState(1);
  return {
    render: () => console.log(count),
    click: () => setCount(count + 1)
  }
}

let app = React.render(Component);
app.click(); // 1
app = React.render(Component);
app.click(); // 2
app = React.render(Component);
app.click(); // 3

一个简单的 useState 功能已经实现了,可以看看效果

到这里,我们对hooks还一些疑问,为了避免篇幅过长,可以查看 深入了解react hooks原理Ⅱ

  • 使用多个 useState 是否能够符合预期
  • useEffect 是怎么实现的