三、useState深入学习

三、useState

1、基本使用:

const [state,setState]=useState(initValue);
  • useState函数接收一个初始化参数initialState,其返回值用数组解构出两个参数:state和setState。
  • 在初始化渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
  • setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。
  • 在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。

注意: React 会确保 setState 函数的标识是稳定的,并且不会在组件重新渲染时发生变化。这就是为什么可以安全地从 useEffectuseCallback 的依赖列表中省略 setState

2、两种更新方式

useState的更新state分为两种,直接更新和函数式更新。

  • 直接更新
    • 这个很好理解,直接传入新的state值即可。
    • 这种情况适用于与返回的新值与旧值不存在依赖关系
setState(newState)
  • 函数式更新
    • 这个新的state的值需要用一个函数来返回
    • preState总能拿到上一次更新成功之后的最新状态
setState((preState)=>{return nextState})

区别:

如果正常使用,这两种方式没啥区别,但是如果是异步更新的话,他们之间的差别就会体现出来了

function Counter() {
  const [count, setCount] = useState(0);
  function handleClick() {
    setTimeout(() => {
      setCount(count + 1)
    }, 3000);
  }
  function handleClickFn() {
    setTimeout(() => {
      setCount((prevCount) => {
        return prevCount + 1
      })
    }, 3000);
  }
  return (
    <>
      Count: {count}
      <button onClick={handleClick}>+</button>
      <button onClick={handleClickFn}>+</button>
    </>
  );
}

​ 当设置为异步更新,点击按钮延迟到3s之后去调用setCount函数,当快速点击按钮时,也就是说在3s多次去触发更新,但是只有一次生效,因为 count 的值是没有变化的。

​ 而当使用函数式更新 state 的时候,这种问题就没有了,因为它可以获取之前的 state 值,也就是代码中的 prevCount 每次都是最新的值。

​ 这是因为使用函数更新的时候,setCount函数将会被放在一个任务队列中,每一个setCount函数都可以拿到上一次更新成功后状态值。

注意:

  • 如果你的更新函数的返回值完全与当前的state相同,则随后的渲染就会跳过。
  • hooks中的useState与class中setState不同,useState 不会自动合并更新对象,这里可以使用函数式的setState结合展开运算符达到合并的目的
  • useReducer 是另一种可选方案,它更适合用于管理包含多个子值的 state 对象

initValue是初始的state,可以是任意的数据类型,也可以是一个有返回值的回调函数(惰性化state)

  • initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

3、惰性初始化state

initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

4、跳过state更新

  • 调用 State Hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is 比较算法 来比较 state。)
  • 需要注意的是,React 可能仍需要在跳过渲染前渲染该组件。
  • 不过由于 React 不会对组件树的“深层”节点进行不必要的渲染,所以大可不必担心。
  • 如果你在渲染期间执行了高开销的计算,则可以使用 useMemo 来进行优化。

5、useState的一些常见问题

当组件中出现多个状态的时候该怎么办?
  • 组件中出现多个状态就意味着useState函数就会被多次调用。
  • useState函数接收的参数并没有规定接收什么类型的参数,它只是被作为一个初始值。
  • 我们之前在class组件中使用setState方法,是将现在的数据与之前的state数据进行合并,然后返回为一个新状态。
  • 而hooks调用setState方法是将之前的state数据进行替换,返回一个新状态。
  • 当然hooks也提供了useReducer()函数,以redux的方式来管理state.

**问题:**在开发中我们可以发现,无论useState()函数被调用了多少次,他们之前都是互相不影响,都是相互独立的。这一点很重要。那么这是如何做到的呢?

React是根据useState的调用顺序来执行的。在React的内部声明了一个数组,按照按照其useState()函数调用的顺序,来保存其数据。所以,一定要在函数的外层保存useState,不能在if else,for循环中,子函数中调用useState.

因为React共享一个保存数据的数组,那么也会对其他函数组件也有影响。Capture Value特性的产生是在于每一次 重新render 的时候,都是重新去执行函数组件了,对于之前已经执行过的函数组件,并不会做任何操作。

来实现一个简单的useState函数:

let memoizedState=[];//hooks存放在这个数组
let cursor=0;//当前 state 下标

function useState(initalValue){
  memoizedState[cursor]=state[cursor]||initalValue;
  const currentCursor=cursor;
  function setState(newState){
   memoizedState[currentState]=newState;
    render();
  }
  return [memoizedState[cursor++],setState];
}

React是如何实现的?

React 中是通过类似单链表的形式来代替数组的。通过 next 按顺序串联所有的 hook。

type Hooks = {
  memoizedState: any, // 指向当前渲染节点 Fiber
  baseState: any, // 初始化 initialState, 已经每次 dispatch 之后 newState
  baseUpdate: Update<any> | null,// 当前需要更新的 Update ,每次更新完之后,会赋值上一个 update,方便 react 在渲染错误的边缘,数据回溯
  queue: UpdateQueue<any> | null,// UpdateQueue 通过
  next: Hook | null, // link 到下一个 hooks,通过 next 串联每一 hooks
}

memoizedState,cursor 是存在哪里的?如何和每个函数组件一一对应的?

我们知道,react 会生成一棵组件树(或Fiber 单链表),树中每个节点对应了一个组件,hooks 的数据就作为组件的一个信息,存储在这些节点上,伴随组件一起出生,一起死亡。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
React Hook是React 16.8版本引入的一种新的特性,它可以让你在无需编写类组件的情况下使用状态和其他React特性。TypeScript是一种静态类型检查的JavaScript超集,可以帮助我们在开发过程中发现并修复潜在的错误。 要深入学习React Hook和TypeScript技术栈,你可以按照以下步骤进行: 1. 学习React基础知识:在学习React Hook之前,确保你对React的基础知识有一定的了解。理解React组件、生命周期、状态管理等概念是很重要的。 2. 学习TypeScript基础知识:如果你还不熟悉TypeScript,可以先学习一些基础知识,比如类型注解、接口、泛型等。掌握这些概念可以帮助你更好地使用TypeScript进行开发。 3. 学习React Hook:阅读React官方文档中关于React Hook的内容,并尝试编写一些简单的Hook。掌握useState、useEffect、useContext等常用的Hook函数,并理解它们的使用方法和原理。 4. 使用TypeScript编写React Hook:在掌握了React Hook的基本知识后,你可以开始使用TypeScript编写React Hook。使用TypeScript可以为你的代码提供类型检查和智能提示,减少潜在的错误。 5. 实践项目:选择一个小型的项目或者练习,使用React Hook和TypeScript进行开发。通过实践项目可以帮助你更好地理解和掌握这两个技术栈。 6. 深入学习进阶内容:一旦你对React Hook和TypeScript有了基本的了解,你可以进一步学习一些进阶内容,比如自定义Hook、使用第三方库、使用Context API等。 记住,深入学习任何技术栈都需要时间和实践。通过不断地阅读文档、编写代码和解决问题,你会逐渐掌握React Hook和TypeScript技术栈。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码搬运工_田先森

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值