当前位置:   article > 正文

React——浅析useState原理_react usestate原理

react usestate原理

在讲解之前我们先回忆下闭包,什么是闭包呢?

闭包是一个特殊的对象 它由两部分组成,执行上下文A以及在A中创建的函数B

当B执行时,如果访问了A中的变量对象,那么闭包就会产生。

这里有一篇文档对闭包有详细描述和解释:https://juejin.cn/post/6844904014232944648

为什么要谈到闭包呢?

答:闭包是react hooks的核心。

进入主题

首先看下我们怎么使用hooks的呢?
const [n, setN] = useState(0)

useEffect(()=>{
	console.log('useEffect')
},[])

const ref = useRef(null);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

手写一个简易版 useState 的代替品 useMyState

import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

// 在当前模块中定义变量_state
let _state;
const myUseState = (initState) => {
		// 第一次调用时没有初始值,因此使用传入的初始值赋值
    _state = ( _state === undefined ? initState : _state);
    const setState = (newState) => {
        _state = newState
        // 此方法能触发页面渲染
        render()
    }
    return [_state,setState]
}
const render = () => ReactDOM.render(<App />, rootElement);

function App() {
		// myUseState在App组件内执行,访问了_state变量,这里产生一个闭包
    const [n, setN] = myUseState(0);
    
    // 根据闭包的特性,_state变量会持久存在,当App组件再次触发渲染时候,依然能够获取上一次_state值
    
    return (
        <div className="App">
            <p>{n}</p>
            <p>
                <button onClick={() => setN(n + 1)}>+1</button>
            </p>
        </div>
    );
}
ReactDOM.render(<App />, rootElement);
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 但是这样的代码也会遇到问题,如果除了 n 还有个 m 呢?

    useMyState 方法进阶版

import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

let _state = [];
let index = 0;
const myUseState = (initState) => {
  let currentIndex = index;
  _state[currentIndex] = ( _state[currentIndex] === undefined ? initState : _state[currentIndex]);
  const setState = (newState) => {
    _state[currentIndex] = newState
    index = 0
    render()
  }
  index += 1
  return [_state[currentIndex],setState]
}
const render = () => ReactDOM.render(<App />, rootElement);

function App() {
  const [n, setN] = myUseState(0);
  const [m, setM] = myUseState(0);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
      </p>
      <p>{m}</p>
      <p>
        <button onClick={() => setM(m + 1)}>+1</button>
      </p>
    </div>
  );
}
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

在上面的useMyState中,我们使用了数组来存放多个hook,然后react源码中使用了更高级的存储结构。

react用了链表这种数据结构来存储 函数组建里面的 hooks

{
    baseQueue: null,     // 当前 update
    baseState: 'hook1',  // 初始值,即 useState 入参
    memoizedState: null, // 当前状态(更新时表示上一次的状态)
    queue: null,         // 待执行的更新队列(queue.pending)
    next: {              // 下一个 hook
        baseQueue: null,
        baseState: null,
        memoizedState: 'hook2',
        next: null
        queue: null
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

**hooks 以链表的形式存储在fiber节点的memoizedState属性上 **

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gUZAW6IT-1653354708608)(/Users/jianshuangpeng/Library/Application Support/typora-user-images/image-20220124173020063.png)]

源码分析:https://github.com/facebook/react/blob/main/packages/react/src/ReactCurrentDispatcher.js

举例说明,下面我们书写了几个hook

function App(){
    const [count, setCount] = useState(1)
    const [name, setName] = useState('chechengyi')
    useEffect(()=>{
        
    }, [])
    const text = useMemo(()=>{
        return 'ddd'
    }, [])
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

分析:

1、在组件第一次渲染的时候,为每个hooks都创建了一个对象, 最终形成了一个链表.

2、在组件更新的过程中,hooks函数执行的顺序是不变的,就可以根据这个链表拿到当前hooks对应的Hook对象,函数式组件就是这样拥有了state的能力。

注意:所以,知道为什么不能将hooks写到if else语句中了把?因为这样可能会导致顺序错乱,导致当前hooks拿到的不是自己对应的Hook对象。

闭包陷阱问题

在上面实现useState中,使用了闭包来存储state值,这就难免会引发一系列闭包问题,如内存溢出、闭包陷阱等。

举例什么是闭包陷阱?

function App(){
    const [count, setCount] = useState(1);
    useEffect(()=>{
        setInterval(()=>{
            console.log(count)
        }, 1000)
    }, [])
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面例子中中,我们持续触发setCount函数+1处理10次,等待1秒后打印结果还是1,那么为什么呢

每一次 render 都会生成一个闭包,每个闭包都有自己的 state 和 props(所以在异步函数中访问hooks的state值拿到的是当前的闭包值并不是最新的state值),所以打印出来的是1;

如何解决这个问题呢?

第一,对useEffect添加依赖count
function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      alert("You clicked on: " + count);
    }, 3000);
  }, [count])

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
第二,使用 useRef 解决闭包陷阱的问题
const FunctionComponent = () => {
  const [value, setValue] = useState(1)
  const countRef = useRef(value)

  useEffect(() => {
    countRef.current = value
  }, [value])

  const log = useCallback(
    () => {
      setTimeout(() => {
        alert(countRef.current)
      }, 1000);
    },
    [value],
  )

  return (
    <div>
      <p>FunctionComponent</p>
      <div>value: {value}</div>
      <button onClick={() => setValue(value + 1)}>add</button>
      <br/>
      <button onClick={log}>alert</button>
    </div>
  )
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

原理:useRef 每次 render 时都会返回同一个引用类型的对象,我们设置值和读取值都在这个对象上处理,这样就能获取到最新的 value 值了。

注意:createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/411805
推荐阅读
相关标签
  

闽ICP备14008679号