usestate 数组增加_React Hooks 源码解析(3):useState

  • React 源码版本: v16.11.0

  • 源码注释笔记:airingursb/react

在写本文之前,事先阅读了网上了一些文章,关于 Hooks 的源码解析要么过于浅显、要么就不细致,所以本文着重讲解源码,由浅入深,争取一行代码也不放过。那本系列讲解第一个 Hooks 便是 useState,我们将从 useState 的用法开始,再阐述规则、讲解原理,再简单实现,最后源码解析。另外,在本篇开头,再补充一个 Hooks 的概述,前两篇限于篇幅问题一直没有写一块。

注:距离上篇文章已经过去了两个月,这两个月业务繁忙所以没有什么时间更新该系列的文章,但 react 这两个月却从 16.9 更新到了 16.11,review 了一下这几次的更新都未涉及到 hooks,所以我也直接把源码笔记这块更新到了 16.11。

1. React Hooks 概述

Hook 是 React 16.8 的新增特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。其本质上就是一类特殊的函数,它们约定以 use 开头,可以为 Function Component 注入一些功能,赋予 Function Component 一些 Class Component 所具备的能力。

例如,原本我们说 Function Component 无法保存状态,所以我们经常说 Stateless Function Component,但是现在我们借助 useState 这个 hook 就可以让 Function Component 像 Class Component 一样具有状态。前段时间 @types/react 也将 SFC 改成了 FC。

1.1 动机

在 React 官网的 Hook 简介中列举了推出 Hook 的原因:

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

  2. 复杂组件变得难以理解

  3. 难以理解的 class

一,组件之间复用状态逻辑很难。是我们系列第二篇中一直讨论的问题,此处不再赘述。

二,复杂组件变得难以理解,即组件逻辑复杂。主要是针对 Class Component 来说,我们经常要在组件的各种生命周期中编写代码,如在 componentDidMount 和 componentDidUpdate 中获取数据,但是在 componentDidMount 中可能也包括很多其他的逻辑,使得组件越开发越臃肿,且逻辑明显扎堆在各种生命周期函数中,使得 React 开发成为了“面向生命周期编程”。而 Hooks 的出现,将这种这种“面向生命周期编程”变成了“面向业务逻辑编程”,使得开发者不用再去关心本不该关心的生命周期。

三,难以理解的 class,表现为函数式编程比 OOP 更加简单。那么再深入一些去考虑性能,Hook 会因为在渲染时创建函数而变慢吗?答案是不会,在现在浏览器中闭包和类的原始性能只有在极端场景下又有有明显的区别。反而,我们可以认为 Hook 的设计在某些方面会更加高效:

  1. Hook 避免了 class 需要的额外开支,像是创建类实例和在构造函数中绑定事件处理器的成本。

  2. 符合语言习惯的代码在使用 Hook 时不需要很深的组件树嵌套。这个现象在使用高阶组件、render props、和 context 的代码库中非常普遍。组件树小了,React 的工作量也随之减少。

其实,React Hooks 带来的好处不仅是更函数式、更新粒度更细、代码更清晰,还有以下三个优点:

  1. 多个状态不会产生嵌套,写法还是平铺的:如 async/await 之于 callback hell 一样,hooks 也解决了高阶组件的嵌套地狱问题。虽然 renderProps 也可以通过 compose 解决这个问题,但使用略为繁琐,而且因为强制封装一个新对象而增加了实体数量。

  2. Hooks 可以引用其他 Hooks,自定义 Hooks 更加灵活。

  3. 更容易将组件的 UI 与状态分离。

1.2 Hooks API

  • useState

  • useEffect

  • useContext

  • useReducer

  • useCallback

  • useMemo

  • useRef

  • useImperativeHandle

  • useLayoutEffect

  • useDebugValue

  • useResponder

以上 Hooks API 都会在未来一一讲解,此处不再赘述。本文先讲解 useState。

1.3 自定义 Hooks

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。这里安利一个网站:https://usehooks.com/,里面收集了实用的自定义 Hooks,可以无缝接入项目中使用,充分体现了 Hooks 的可复用性之强、使用之简单。

2. useState 的用法与规则

import React, { useState } from 'react'

const App: React.FC = () => {

const [count, setCount] = useState<number>(0)

const [name, setName] = useState<string>('airing')

const [age, setAge] = useState<number>(18)

return (

<>

<p>You clicked { count} timesp>

<button onClick={() => {

setCount(count + 1)

setAge(age + 1)

}}>

Click me

button>

>

)

}

export default App

如果用过 redux 的话,这一幕一定非常眼熟。给定一个初始 state,然后通过 dispatch 一个 action,再经由 reducer 改变 state,再返回新的 state,触发组件重新渲染。

它等价于下面这个 Class Component:

import React from 'react'

class Example extends React.Component {

constructor(props) {

super(props);

this.state = {

count: 0,

age: 18,

name: 'airing'

};

}

render() {

return (

<>

<p>You clicked { this.state.count} timesp>

<button onClick={() => this.setState({

count: this.state.count + 1,

age: this.state.age + 1

})}>

Click me

button>

>

);

}

}

可以看到 Function Component 比 Class Component 简洁需要,useState 的使用也非常简单。但需要注意的是,Hooks 的使用必须要符合这条规则:确保 Hook 在每一次渲染中都按照同样的顺序被调用。因此最好每次只在最顶层使用 Hook,不要在循环、条件、嵌套函数中调用 Hooks,否则容易出错。

那么,为什么我们必须要满足这条规则?接下来,我们看一下 useState 的实现原理并自己亲自动手实现一个 useState 便可一目了然。

3. useState 的原理与简单实现

3.1 Demo 1: dispatch

第二节中我们发现 useState 的用法蛮像 Redux 的,那我们基于 Redux 的思想,自己动手实现一个 useState:

function useState(initialValue) {

let state = initialValue

function dispatch(newState) {

state = newState

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

}

return [state, dispatch]

}

我们将从 React 中引入的 useState 替换成自己实现的:

import React from 'react'

import { render } from 'react-dom'

function useState(initialValue: any) {

let state = initialValue

function dispatch(newState: any) {

state = newState

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

}

return [state, dispatch]

}

const App: React.FC = () => {

const [count, setCount] = useState(0)

const [name, setName] = useState('airing')

const [age, setAge] = useState(18)

return (

<>

You clicked { count} timesp>

Your age is { age}p>

Your name is { name}p>

<button onClick={() => {

setCount(count + 1)

setAge(age + 1)

}}>

Click me

button>

>

)

}

export default App

这个时候我们发现点击按钮不会有任何响应,count 和 age 都没有变化。因为我们实现的 useState 并不具备存储功能,每次重新渲染上一次的 state 就重置了。这里想到可以在外部用个变量来存储。

3.2 Demo 2: 记忆 state

基于此,我们优化一下刚才实现的 useState:

let _state: any

function useState(initialValue: any) {

_state = _state | initialValue

function setState(newState: any) {

_state = newState

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

}

return [_state, setState]

}

虽然按钮点击有变化了,但是效果不太对。如果我们删掉 age 和 name 这两个 useState 会发现效果是正常的。这是因为我们只用了单个变量去储存,那自然只能存储一个 useState 的值。那我们想到可以用备忘录,即一个数组,去储存所有的 state,但同时我们需要维护好数组的索引。

3.3 Demo 3: 备忘录

基于此,我们再次优化一下刚才实现的 useState:

let memoizedState: any[] = [] // hooks 的值存放在这个数组里

let cursor = 0 // 当前 memoizedState 的索引

function useS

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值