React学习笔记——Hooks中useState的基础介绍和使用

在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。使用 React Hooks 相比于从前的类组件有以下几点好处:

  1. 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
  2. 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现

Hook 是 React 16.8.0 版本增加的新特性,可以在函数组件中使用 state以及其他的 React 特性
Hooks只能在函数式组件中使用,既无状态组件(所有钩子在用时都要先引入)。

Hook 使用规则

Hook 就是JavaScript 函数,但是使用它们会有两个额外的规则:
1、只能在函数最外层调用 Hook。不要在循环、条件判断或者嵌套函数(子函数)中调用。

(参考:https://blog.csdn.net/weixin_34413802/article/details/91475789)

2、只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
3、在多个useState()调用中,渲染之间的调用顺序必须相同

useState()

useState() 在函数组件引入状态的钩子,Hook 的每次调用都有一个完全独立的 state —— 因此你可以在单个组件中多次调用同一个 Hook。

const [xxx, setXxx] = useState(initValue); // 解构赋值
例如: 
function Counter({
      initialCount}) {
     
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {
     count}
      <button onClick={
     () => setCount(initialCount)}>Reset</button>
      <button onClick={
     () => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={
     () => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

count 只在组件首次渲染的时候被创建,通过调用 setCount 来更新当前的 count。
组件重新渲染,useState 返回给我们当前最新的count值。

(1)参数:

第一次初始化指定的值在内部作缓存。可以按照需要使用数字字符串对其进行赋值,而不一定是对象。没有规定一定要是string/number/boolean这种简单数据类型,它完全可以接收对象或者数组作为参数。

(2)返回值:

包含 2 个元素的数组,第 1 个为内部当前状态值,第 2 个为更新状态值的函数,一般直接采用es6里面的解构赋值获取。

(3)假如一个组件有多个状态值怎么办?

如果想要在state中存储多个不同的变量,只需调用 useState() 多次即可。

 const [age, setAge] = useState(42);
 const [fruit, setFruit] = useState('banana');
 const [todos, setTodos] = useState([{
      text: 'Learn Hooks' }]);

useState无论调用多少次,相互之间是独立的

这一点至关重要,那么react怎么保证多个useState之间是相互独立的?

比如上面的例子,我们调用了三次useState,每次我们传的参数只是一个值(如42,‘banana’),我们根本没有告诉react这些值对应的key是哪个,那react是怎么保证这三个useState找到它对应的state呢?

答案是:react是根据useState出现的顺序来定的

  //第一次渲染
  useState(42);  //将age初始化为42
  useState('banana');  //将fruit初始化为banana
  useState([{
      text: 'Learn Hooks' }]); //...

  //第二次渲染
  useState(42);  //读取状态变量age的值(这时候传的参数42直接被忽略)
  useState('banana');  //读取状态变量fruit的值(这时候传的参数banana直接被忽略)
  useState([{
      text: 'Learn Hooks' }]); //...

假如我们改一下代码:

let showFruit = true;
function ExampleWithManyStates() {
     
  const [age, setAge] = useState(42);
  
  if(showFruit) {
     
    const [fruit, setFruit] = useState('banana');
    showFruit = false;
  }
 
  const [todos, setTodos] = useState([{
      text: 'Learn Hooks' }]);

这样一来

  //第一次渲染
  useState(42);  //将age初始化为42
  useState('banana');  //将fruit初始化为banana
  useState([{
      text: 'Learn Hooks' }]); //...

  //第二次渲染
  useState(42);  //读取状态变量age的值(这时候传的参数42直接被忽略)
  // useState('banana');  
  useState([{
      text: 'Learn Hooks' }]); //读取到的却是状态变量fruit的值,导致报错

所以,react有规定:我们必须把hooks写在函数的最外层,不能写在ifelse等条件语句当中,来确保hooks的执行顺序一致

(4)setXxx() 的写法

1、setXxx(newValue):参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值。

2、setXxx(value => newValue):参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值

(5)惰性初始 state(官网介绍)

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

const [state, setState] = useState(() => {
     
  const initialState = someExpensiveComputation(props);
  return initialState;
});
function App() {
     
  const [obj, setObject] = useState({
     
    count: 0,
    name: "alife"
  });
  return (
    <div className="App">
      Count: {
     obj.count}
      <button onClick={
     () => setObject({
      ...obj, count: obj.count + 1 })}>+</button>
      <button onClick={
     () => setObject({
      ...obj, count: obj.count - 1 })}>-</button>
    </div>
  );
}
(6)useState和setState的对比

useState 会返回一对值:当前状态一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。

类似class 组件的 this.setState,但是useState 不会自动合并更新对象不会把新的 state 和旧的 state 进行合并,更新 state 变量是完全替换

如果你想,官网上给出思路,利用展开运算符(···)来达到合并更新对象的效果

const [state, setState] = useState({
     });
setState(prevState => {
     
  // 也可以使用 Object.assign
  return {
     ...prevState, ...updatedValues};
});
(7)useState与class组件的this.state对比

1、useState(initValue),初始值可以是基本数据类型,而不一定是对象;而this.state必须是一个对象
2、创建多个状态,需要多次调用useState();this.state只哟领调用一次即可
3、在useState()中,state和改变状态的函数需要成对获取,如const [count, setCount] = useState();而this.state的改变需要用this.setState改变

(8)过时状态state

闭包是一个从外部作用域捕获变量的函数。

闭包(例如事件处理程序,回调)可能会从函数组件作用域中捕获状态变量。
由于状态变量在渲染之间变化,因此闭包应捕获具有最新状态值的变量。
否则,如果闭包捕获了过时的状态值,则可能会遇到过时的状态问题。

过时状态:

import React ,{
     useState}from 'react';

const App2 = () => {
     
    const [count , setCount] = useState(0)
    const handleClick = () => {
     
        setTimeout(()=>{
     
            setCount(count+1)
            console.log('111')
        },3000)
        console.log('222')
    }
    return (
        <div>
            {
     count}
            <button onClick={
     handleClick}>点击我</button>
        </div>
    );
};

export default App2;

效果图
React学习笔记——Hooks中useState的基础介绍和使用_第1张图片

可以看出,这是一个过时的闭包,它从初始渲染(使用0初始化时)中捕获了过时的count变量。
为了解决这个问题,我们使用函数方法来更新count的状态。
更新状态

import React ,{
     useState}from 'react';

const App2 = () => {
     
    const [count , setCount] = useState(0)
    const handleClick = () => {
     
        setTimeout(()=>{
     
            setCount(count=>count+1)
            console.log('111')
        },3000)
        console.log('222')
    }
    return (
        <div>
            {
     count}
            <br/>
            <button onClick={
     handleClick}>点击我</button>
        </div>
    );
};

export default App2;

效果图
React学习笔记——Hooks中useState的基础介绍和使用_第2张图片
不难看出,count=>count+1正确的更新计数状态。React 确保将最新状态值作为参数提供给更新状态函数,过时闭包的问题解决了。

你可能感兴趣的:(React,react)