浅谈React Hook的使用及原理

325 阅读5分钟
2019年React Hook是React生态圈里边最火的新特性了。它改变了原始的React类的开发方式,改用了函数形式;就是用函数的形式代替原来我们写的那种继承component类的形式,有状态的组件都可以使用函数的形式定义它,原来的那种有状态组件和无状态组件就不那么说了,有状态组件也可以用函数形式来写了。让程序员用起来更轻松。那么今天我们就讲解一下react hooks的实现与简单原理

一、什么是hook?

Hook 是 React 16.8 的新增特性。React Hook 从具象上来说就为函数组件(纯函数)提供副作用能力的 React API,从抽象意义上来说是确定状态源的解决方案。其实就是可以让你在不编写class的情况下使用state以及其他的React特性。

二、为什么使用hook?

1. 避免地狱式嵌套,可读性提高

2. 比class更容易理解

3. 使函数组件存在状态

4. 解决HOC和Render Props的缺点

三、如何用hook?

下面我们就简单讲解一下react hook 和 class组件的区别
我们先写一个用class写简单的组件

import React, { Component } from 'react'

class Demo extends Component {
    constructor(props) {
        super(props)
        this.state = {
            count : 0
        }
        this.setCount = this.setCount.bind(this)
    }
    setCount () {
        this.setState({
            count: this.state.count + 1
        })
    }

  componentDidMount () {
      console.log(`componentDidMount => you click ${this.state.count} times`)
    }

    componentDidUpdate () {
      console.log(`componentDidUpdate => you click ${this.state.count} times`)
    }

    render () {
        return (
            <div>
                You click {this.state.count} times
                <button onClick={this.setCount}>click me</button>
            </div>
        )
    }
}

export default Demo

再用hook改写下

import React, { useState, useEffect } from 'react'

function Demo2 () {
    const [ count, setCount ] = useState(0)
    useEffect(() => {
        console.log(`useEffect => you click ${count} times`)
    })

    return (
        <div>
            You click {count} times
            <button onClick={() => setCount(count + 1)}>click me</button>
        </div>
    )
}

export default Demo2

以上两种写法的对比,不难看出从代码量及易理解程度上,hook的写法更有优势。

下面我们再加工一下代码,来看一下class的生命周期与hooks的useEffect 之间的区别

useEffect是异步的不影响视图更新,有利有弊,若需要实时计算页面大小会出现问题

更详细的使用方法这里就不多做介绍了,可以去官网查找相关使用方法进行练习,使用方法都很简单,Hook的API还有useContext等,这里就不一一举例了,可以私下自己练习一下。

以上是hook的简单用法,这里就不多做叙述,当我们使用或者看官方文档时,我们可能会有很多疑惑,例如为什么不要在循环、条件判断或者子函数中调用等等一些问题,下面我们就讲解一下useState及useEffect机制,了解了其中的原理,自然就能解开我们的疑惑。

四、useState及useEffect的机制

1、useState

useState是hooks方案可以实现的基础,它让我们可以不依赖class的情况下,是组件可以有自己的状态

useState 最简单的用法是这样的:

const [count, setCount] = useState(0) 
  • useState 就是一个函数;

  • 每次更新其实都是触发了它的重执行(因为整个函数组件都重执行了)。(这里是es6解构赋值, useState 是个函数,返回值是个数组)

下面的代码就是其简单的实现:

function useState(initVal) {
    const state = initVal;
    const setState = newState => state = newState;
    return [state, setState]
}

这只是初始的赋值,可还不能更新数据,即便是触发了组件更新最后也只是再拿了一遍初始值。因为每次useState的state都是局部变量,触发更新后也是重新创建一次,所以我们需要把state存起来,这样更新的时候能够拿到更新后的值。最简单的就是全局变量,当然React并不是这么实现的,对于 hooks,React 采用的是链表,感兴趣的可以自己去看看,这里只是模拟实现一下。

const Hooks = []; // 全局的存储hook的变量
let currentIndex = 0; // 全局的  依赖hook执行顺序的下标 
function useState (initVal) {
    hooks[currentIndex] = hooks[currentIndex] || initVal; //判断一下是否需要初始化
    const _currentIndex = currentIndex // currentIndex 是全局可变的,需要保存本次执行顺序的下标
    const setState = newState => hooks[_currentIndex] = newState // 赋值 更新数据
    return [hooks[currentIndex++], setState] // 为了多次调用hook, 每次执行currentIndex需要+1
    
}

将所有hooks都保存在一个全局数组变量中,每次更新时改变对应下标的数组内容就好,这就是为什么要保证hooks的执行顺序在更新前后一致,如果更新的时候出现条件判断,可能本该执行第二个的hooks因为条件判断更新了第三个,而原本第二个hook没有执行,而第三个的值也是错误的。

2、useEffect

useEffect 充当了class中生命周期的作用,与useState的原理大致是一样的,多了一个依赖数组,如果依赖数组总的每一项都和上次一样,就不需要更新,反之更新

useEffect(() => {
    console.log('test')
}, [count])
// 接收两个参数  callback 回调  depArray 依赖数组  
function useEffect (callback, depArray) { //depArray => [count] 传入的是改变的值
    const hasNodeps = !depArray // 判断是否有依赖数组  如果没有每次都更新
    const hook = hooks[currentIndex] || {} // hook 是 {}
    const deps = hook && hook.deps // undefined
    const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true // 初始化时执行  或者当依赖数组中有变化时更新
    if (hasChangedDeps || hasNodeps) {
        callback && callback()
        hooks[currentIndex].deps = depArray
    }
    currentIndex++ 
}

下面用图简单讲解下过程

1.初始化及初次渲染 2.执行 3.ReRender

以上就是useState及useEffect的原理,当然react中,实现方式还是有差异的。react中是通过类似单链表的形式来替代数组的。这里就只做简单的原理讲解。 好了,以上就是今天对react Hooks相关的讲解,如果有其他问题,同学们可以私下找我探讨。谢谢大家。