React 基础 - useState、useContext/createContext

前言

本文新手向解释了 react useState 的更新逻辑和基本使用方法,需要了解真实且具体的实现原理,请移步Fiber。

useState

ts 复制代码
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];

基本Demo

tsx 复制代码
function FunctionComponent(props: any) {
  const [count1, setCount1] = useState()
  const [count2, setCount2] = useState(() => props.count || 0)
  
  useEffect(() => {
      setCount1(prevState => prevState + 1)
  }, [])
  
  return <div>{count1}</div>
}

没什么好说的,基本用法就这些。注意点:

  • 基本使用规范:仅可在组件或hook顶层调用useState来声明一个变量。不可在循环或条件语句中调用,这里不做过多说明。

  • set函数仅会影响下一次渲染中state返回的内容,

  • 多次执行set仅会返回最后一次执行结果。

  • 其它要点请仔细看下面示例,以时间线的形式描述了state更新时机、浏览器更新时机及副作用执行时机,同时解释了小白常见两个问题:

    • state更新缺无法获取最新值;
    • state更新死循环:
    tsx 复制代码
    function FunctionComponent() {
      const [count, setCount] = useState(0)
    
      const handleClick1 = () => {
        setCount(count + 1)
        setCount(count + 1)
        console.log('handleClick1: ', count)
        setCount(count + 1)
      }
      const handleClick2 = () => {
        setCount(count => count + 1)
        setCount(count => { 
          console.log('setCount: ', count)
          return count + 1
        })
        console.log('handleClick2: ', count)
        setCount(count => count + 1)
      }
    
      useEffect(() => { 
        setCount(1)
        console.log('effect1', count)
      }, [])
    
      useEffect(() => console.log('effect2', count), [count])
    
      console.log('function body: ', count)
    
      return <div>
        <button type='button' onClick={handleClick1}>button1: {count}</button>
        <button type='button' onClick={handleClick2}>button2: {count}</button>
      </div>
    }
    • 注:Fiber真实逻辑与下文有差异,这里仅为更方便理解。
    • 初始化执行函数
      • 第一次执行函数
      • 初始化count,useState 返回状态值0
      • 定义handleClick1,handleClick2
      • 缓存effect1,缓存effect2
      • function body:打印状态值为0
      • 浏览器渲染 count 状态值为 0
      • 执行effect1回调,记录 count 更新值 为1
      • effect1 打印当前状态值为0
      • 执行effect2回调
      • effect2 打印当前状态值为0
      • 副作用执行完毕,更新状态值为记录值:1
      • 第二次执行函数
      • useState 返回状态值1
      • 定义handleClick1,handleClick2
      • effect1无动作,effect2依赖更新,缓存effect2
      • function body:打印状态值为1
      • 浏览器渲染 count 状态值为 1
      • 执行effect2回调
      • effect2 打印当前状态值为1
      • 副作用执行完毕,无需更新渲染层。
    • 点击 button1
      • 执行handleClick1,当前状态值为1
        • 记录 count 更新新值 为1 + 1
        • 记录 count 更新新值 为1 + 1
        • handleClick1 打印当前状态值为1
        • 记录 count 更新新值 为1 + 1
      • 顺序更新三次记录值
        • count 记录值更新值为2
        • count 记录值更新值为2
        • count 记录值更新值为2
      • 更新状态值为记录值:2
      • 第三次执行函数
        • useState 返回状态值2
        • 定义handleClick1,handleClick2
        • effect1无动作,effect2依赖更新,缓存effect2
        • function body:打印状态值为2
        • 浏览器渲染 count 状态值为 2
        • 执行effect2回调
        • effect2 打印当前状态值为2
        • 副作用执行完毕,无需更新渲染层。
    • 点击 button2
      • 执行handleClick2,当前状态值为2
        • 记录 count 更新函数:count => count + 1
        • 记录 count 更新函数:count => { console.log('setCount: ', count); return count + 1 }
        • handleClick2 打印当前状态值为2
        • 记录 count 更新函数:count => count + 1
      • 顺序执行三个更新函数,将记录值作为入参,将返回值作为新记录值
        • 第一次:当前记录值为2,count 记录值更新值为2 + 1
        • 第二次:setCount 打印当前记录值为3,count 记录值更新值为3 + 1
        • 第一次:当前记录值为4, count 记录值更新值为4 + 1
      • 更新状态值为记录值:5
      • 第四次执行函数
        • useState 返回状态值5
        • 定义handleClick1,handleClick2
        • effect1无动作,effect2依赖更新,缓存effect2
        • function body:打印状态值为5
        • 浏览器渲染 count 状态值为 5
        • 执行effect2回调
        • effect2 打印当前状态值为5
        • 副作用执行完毕,无需更新渲染层。

useContext

ts 复制代码
function createContext<T>(defaultValue: T): Context<T>;
function useContext<T>(context: Context<T>): T;

基本DEMO

tsx 复制代码
const Context = createContext()

function Demo() { 
  return (
    <Context.Provider value={sameValue}>
      <Child/>
    </CountContext2.Provider>
  )
}

function Child() {
  const value = useContext(Context)
  return <div>{value}</div>
}

没什么好说的,基本用法就这些。存值取值。后续更新状态管理会涉及到 useContextuseState的碰撞结果。

相关推荐
胡萝卜术2 小时前
从API调用到手写LRU:力扣146「LRU缓存」的哈希表+双向链表终极进化之路
前端·javascript·面试
科技道人2 小时前
记录 默认置灰/禁用 app ‘Search Engine Selector‘ 的disable按钮
开发语言·前端·javascript
天疆说2 小时前
Zotero Connector 在 Edge 里找不到桌面 Zotero(Linux / Zotero 9.0.6 / Edge 150)
linux·前端·edge
李伟_Li慢慢3 小时前
02-从硬件说起WebGL
前端·three.js
kyriewen4 小时前
看了微软几万人用AI编程的数据——效率涨24%的代价没人提
前端·ai编程·claude
2601_963771374 小时前
How to Scale Your WordPress Store Traffic in 2026
前端·php·plugin
亿元程序员5 小时前
老板说我的3D箭头游戏用来做试玩太普通了,没人想玩,让我变点花样...
前端
李伟_Li慢慢5 小时前
01-threejs架构原理-课程简介
前端·three.js
_瑞7 小时前
深入理解 iOS 渲染原理
前端·ios
IT_陈寒7 小时前
SpringBoot自动配置失灵?你可能忘了这个关键注解
前端·人工智能·后端