React hooks重温之useState

64 阅读4分钟

前情

React和Vue是目前前端流行的主要框架,平时工作中都有用到,对于新版的react来说,函数组件和hooks是react的重中之重,特别是hooks给react带来了非常棒的开发体验,工作中疲于业务开发,停留在会使用相关hooks的阶段,并未细细学习react相关hooks,现在正是细细学习的时候了。

useState

是react hooks中最基础同时也是使用量最多的一个,它能让函数组件拥有自己的状态,通过useState可以实现状态的初始化、读取、更新。

基础语法

const [状态名,set函数] = useState(初始值)

其中状态名代表的数据,可以被函数组件使用,如果修改状态名所表示的数据,需要调用set函数,状态变化会触发函数组件的重新执行。

示例代码:

import { useState } from 'react';

export default function Count() {
  const [count, setCount] = useState(0);
	console.log('Count run');
  return (
    <div className='App'>
      <p>当前count的值:{count}</p>
      <button onClick={() => setCount(count + 1)}>count++</button>
    </div>
  );
}

以函数的形式为状态赋初始值

在使用useState定义状态时,除了可以直接给定初始值,还可以通过函数返回值的形式为状态赋初始值,语法如下:

const [msg, setMsg] = useState(() ⇒ 初始值)

示例代码:

import { useState } from 'react';

export default function StateFunction() {
  const [date, setDate] = useState(() => {
    const dateObj = new Date();
    console.log('-- useState init --');
    return {
      Y: dateObj.getFullYear(),
      M: dateObj.getMonth() + 1,
      D: dateObj.getDate(),
      h: dateObj.getHours(),
      m: dateObj.getMinutes(),
      s: dateObj.getSeconds(),
    }
  });

  /**
   * 更新状态date
   */
  const updateDate = () => {
    const dateObj = new Date();
    setDate({
      Y: dateObj.getFullYear(),
      M: dateObj.getMonth() + 1,
      D: dateObj.getDate(),
      h: dateObj.getHours(),
      m: dateObj.getMinutes(),
      s: dateObj.getSeconds(),
    })
  }
  return (
    <div className='App'>
      <p>今天日期是:{date.Y}年{date.M}月{date.D}日{date.h}时{date.m}分{date.s}秒</p>
      <button onClick={updateDate}>现在时间</button>
    </div>
  );
}

注:只有组件初始化的时候才会执行状态的初始方法

useState是异步变更状态的

调用useState()会返回一个变更状态的函数,这个函数内部是以异步的形式修改状态的,所以修改状态后无法立即拿到最新的状态。

示例代码:

import { useState } from 'react';

export default function Count() {
  const [count, setCount] = useState(0);
  console.log('CountAsync run');

  const updateCount = () => {
    setCount(count + 1);
		// 这是拿到的更新前的值
    console.log('---- updateCount ----:', count);
  }
  return (
    <div className='App'>
      <p>当前count的值:{count}</p>
      <button onClick={updateCount}>count++</button>
    </div>
  );
}

通过useEffect hooks可以监听状态值的变化,并执行相应的回函数,示例代码如下:

import { useState, useEffect } from 'react';

export default function CountAsyncByUseEffect() {
  const [count, setCount] = useState(0);
  console.log('CountAsyncByUseEffect run');

  const updateCount = () => {
    setCount(count + 1);
    console.log('---- updateCount ----:', count);
  }

  useEffect(() => {
    console.log('---- useEffect  ----:count变化了', count);
  }, [count]);
  return (
    <div className='App'>
      <p>当前count的值:{count}</p>
      <button onClick={updateCount}>count++</button>
    </div>
  );
}

连续调用状态更新函数,值更新不及时的问题?

当连续多次以相同的操作更新状态时,React内部会对传递过来的新值进行比较,如果值相同,则会屏蔽后续的更新行为,从而防止组件频繁渲染问题。

问题示例代码:

import { useState } from 'react';

export default function Counting() {
  const [count, setCount] = useState(0);
  console.log('Counting run');

  const updateCount = () => {
    setCount(count + 1);
    setCount(count + 1);
    // 第一次调用后实际上count的值是1不是2
  }

  return (
    <div className='App'>
      <p>Counting-当前count的值:{count}</p>
      <button onClick={updateCount}>count++</button>
    </div>
  );
}

解决方法:状态设置函数传递一个带返回值的函数去更新状态,不是直接传递更新的状态值,

示例代码如下:

import { useState } from 'react';

export default function CountingFunction() {
  const [count, setCount] = useState(0);
  console.log('CountingFunction run');

  const updateCount = () => {
    setCount((countPrev) => countPrev + 1);
    setCount((countPrev) => countPrev + 1);
    // 执行第一次后count值为2
  }

  return (
    <div className='App'>
      <p>Counting-当前count的值:{count}</p>
      <button onClick={updateCount}>count++</button>
    </div>
  );
}

useState更新对象类型的值

如果要更新对象类型的值,并触发组件的重新渲染,则必须展开运算符或者Object.assign(),JSON.parse+JSON.stringify,或者使用第三方像lodash实现对象的拷贝等生成一个新的对象,用新的对象覆盖旧对象,才能正常触发组件的重新渲染,示例代码如下:

import { useState } from 'react';

export default function StateObj() {
  const [useInfo, setUseInfo] = useState({
    name: '小黑',
    sex: '男',
    age: 18
  });
  console.log('StateObj run');

  const updateInfo0 = (name, sex) => {
    useInfo.name = name;
    useInfo.sex = sex;
    setUseInfo(useInfo);
  }
  const updateInfo1 = (name, sex) => {
    setUseInfo({
      ...useInfo,
      name,
      sex
    });
  }
  const updateInfo2 = (name, sex) => {
    setUseInfo(Object.assign({...useInfo}, {
      name,
      sex
    }));
  }
  const updateInfo3 = (name, sex) => {
    const userInfoTemp = JSON.parse(JSON.stringify(useInfo));
    userInfoTemp.name = name;
    userInfoTemp.sex = sex;
    setUseInfo(userInfoTemp);
  }

  return (
    <div className='App'>
      <p>StateObj-当前用户信息:用户名:{useInfo.name}性别:{useInfo.sex}年龄:{useInfo.age}</p>
      <button onClick={() => updateInfo0('小红0', '女0')}>小红0女0</button>
      <button onClick={() => updateInfo1('小红1', '女1')}>小红1女1</button>
      <button onClick={() => updateInfo2('小红2', '女2')}>小红2女2</button>
      <button onClick={() => updateInfo3('小红3', '女3')}>小红3女3</button>
    </div>
  );
}

友情提示

如果你使用的是vs code,在书写React hooks时可以安装第三方的插件,快捷输入相关hooks,我个人有编写一款常用代码段插件**Common-Code-Snippet**,里面就有与hooks相关的代码段,除了hooks相关的代码段,还有很多好用的代码段,具体使用规则可查看插件的说明文挡,插件会一直持续更新。

hooks相关示例说明:

// 输入rusestate会生成如下代码段
const [example, setexample] = useState();

// 输入ruseeffect会生成如下代码段
useEffect(() => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  }, [state]);