函数组件hook--useState

234 阅读5分钟

Hook

React的Hook就是一些React提供的内置函数,这些函数可以让函数组件拥有和类组件一样的组件状态(state)以及模拟类组件的生命周期函数。但是不要什么业务都使用Hook,要在合适的时候使用Hook,否则会造成性能问题。(能不用的时候就不要用,当遇到性能不好优化的时候,自然会想到使用Hook)

所有的Hook只能在函数组件中使用,在自定义的事件函数以及类组件不能使用Hook

useState

useState和类组件的this.state一样,都是用来管理组件状态的。在React的Hook没出来之前,函数组件也叫做Functional Stateless Component(FSC:功能无状态的组件),这是因为函数组件本身的函数体就是render,每次重新渲染时就会重新运行整个函数并生成新的函数作用域。所以同一个函数组件是不能够共用状态的。 而类组件是重新运行render函数,组件状态并不会重新创建。

useState允许函数组件将自己的状态持久化并存储在React运行时的某个地方,这样在函数组件每次重新渲染的时候都可以从这个地方拿到该状态,而且当该状态被更新的时候,组件也会重渲染。

state的创建和更新

image.png

useState基本使用

// App.js
// 函数组件
import { useState } from 'react'
import Son from './son.js';
function App() {
  let msg = useState("I Am Gloria");
  console.log(msg);
  return (
    <>
      <h1>App 组件</h1>
      <Son></Son>
    </>
  )
}
export default App;

image.png

useState函数返回值是一个数组,数组中第一个元素是当前state的最新值,第二个元素是一个方法,该方法是用来更新state的函数,函数传入的值就是要修改的数据,而且调用该函数会刷新页面,并重新取值使用的state,将state修改为最新的数据。

// App.js
import { useState } from 'react'
import Son from './son.js';
function App() {
  let msg = useState("I Am Gloria");
  let change = () => {
    msg[1]('I Am Gloria World Tour');
  };
  let look = function () {
    console.log(msg);
  }
  return (
    <>
      <h1>App 组件</h1>
      <h5>{msg[0]}</h5>
      <button onClick={change}>change</button>
      <button onClick={look}>look</button>
      <Son></Son>
    </>
  )
}
export default App;

2.gif

常用的是通过解构赋值来创建state,效果同理。

// 函数组件
import { useState } from 'react'
import Son from './son.js';
function App() {
  let [msg, setMsg] = useState("I Am Gloria");
  let change = () => {
    setMsg('I Am Gloria World Tour');
  };
  let look = function () {
    console.log(msg, 'msg');
    console.log(setMsg, 'setMsg')
  }
  return (
    <>
      <h1>App 组件</h1>
      <h5>{msg}</h5>
      <button onClick={change}>change</button>
      <button onClick={look}>look</button>
      <Son></Son>
    </>
  )
}
export default App;

2.gif

msgsetMsg这两个变量名是自定义的,msg可用于组件内部使用创建的state数据,setMsg函数用于修改state,当修改后,所有使用过msg的地方都会重新取值(调用render刷新页面)。 一个函数组件中可以用多个useState

useState注意事项

useState可以传入函数

const [ state , dispatch ] = useState(initData)
  • state:作为渲染视图的数据源。
  • dispatch:改变 state的函数,可以理解为函数组件渲染的渲染函数
  • initData有两种情况,第一种情况是非函数,将作为 state 初始化的值。第二种情况是函数,函数的返回值作为 state 初始化的值。这个函数只会被调用一次,用在初始值是动态值的情况。

比如:

// 函数组件
import { useState } from "react"
function App(props) {
  const propsCount = props.count || 0;
  let [count, setCount] = useState(propsCount)
  return (
    <>
      <p>{count}</p>
      <button onClick={() => { setCount(count++) }}>add</button>
    </>
  )
}
export default App;

const propsCount = props.count || 0 是不合理的,因为每次重新渲染函数组件时都会执行这行代码,而这行代码的意图是在初始化的时候执行,所以需要在useState的参数里放入一个函数。传入的函数仅在初始渲染时执行一次,以获得初始状态。

useState更新数据

在使用 useState 的 dispatch 更新 state 的时候,会浅比较两次 state ,发现 state 相同,不会开启更新任务。对于引用数据如果两次 state 指向了相同的引用地址,就会认为 state 相等,所以不会发生视图更新了。

// 函数组件
import { useState } from 'react'
function App() {
  let [obj, setObj] = useState({ name: "G.E.M.", title: "I am gloria world tour" });
  let change = () => {
    obj.name='golden stars';
    setObj(obj);
  };
  let look = function () {
    console.log(obj);
  }
  return (
    <>
      <p>{obj.name}</p>
      <p>{obj.title}</p>
      <button onClick={change}>change</button>
      <button onClick={look}>look</button>
    </>
  )
}
export default App;

2.gif

当状态是一个引用数据时,可以使用展开运算符浅拷贝引用数据,重新申请一个内存空间,然后更新部分状态。

import { useState } from 'react'
function App() {
  let [obj, setObj] = useState({ name: "G.E.M.", title: "I am gloria world tour" });
  let change = () => {
    obj.name='golden stars';
    // {...obj}大括号在js语法中就是创建对象的直接量,此处就是传入了一个装有响应式对象的所有成员的新对象
    setObj({...obj});
  };
  let look = function () {
    console.log(obj);
  }
  return (
    <>
      <p>{obj.name}</p>
      <p>{obj.title}</p>
      <button onClick={change}>change</button>
      <button onClick={look}>look</button>
    </>
  )
}
export default App;

2.gif

useState返回数组中第二个元素用于修改状态的函数是全量替代。 全量替代就是传入的数据会把原来的数据全部替换。  而类组件的浅归并是传入的数据原来有就会更新,原来没有的数据就会创建该数据。

import { useState } from 'react'
function App() {
  let [obj, setObj] = useState({ name: "G.E.M.", title: "I am gloria world tour" });
  let change = () => {
    obj = setObj({ msg: 'golden stars', name: "Gloria" });
  };
  let look = function () {
    console.log(obj);
  }
  return (
    <>
      <p>{obj.name}</p>
      <p>{obj.title}</p>
      <button onClick={change}>change</button>
      <button onClick={look}>look</button>
    </>
  )
}
export default App;

2.gif

传入对象会把原来的覆盖,传入的对象中没有title属性,取属性值就是undefinedundefined不会在页面渲染。

setState可以使用函数形式的:如果需要基于前一个状态来计算新的状态,可以使用函数形式的setState

import React, { useState } from 'react'; 
const Counter = () => { 
    const [count, setCount] = useState(0); 
    // 增加计数器 
    const increment = () => { 
        setCount(count + 1); 
     }; 
    // 减少计数器 
    const decrement = () => { 
        setCount(count - 1); 
    }; 
    // 重置计数器 const reset = () => { 
        setCount(0); 
    }; 
    // 根据最新的值重新赋值 
    const setToLatestValue = () => { 
    // prevCount是前一个状态
    setCount(prevCount => prevCount * 2); 
    };
    return ( 
        <div> <h1>当前计数: {count}</h1> 
        <button onClick={increment}>增加</button> 
        <button onClick={decrement}>减少</button> 
        <button onClick={reset}>重置</button> <button onClick={setToLatestValue}>设置为最新值的两倍</button> </div> ); 
    }; 
export default Counter;

useState是异步操作

useState返回的修改状态的dispatch函数没有回调函数。无论是useState还是类组件的this.setState都是异步调用的,也就是说每次组件调用完之后都不能立即拿到最新的state值。

为了解决这个问题,类组件的this.setState中第一个参数是要修改的数据,第二个参数是一个回调函数,用来获取到最新的state值:

this.setState(newState, state => { console.log( state)})
// 类似于vue的watch监听,在组件更新后useEffect传入的回调函数会执行,此时拿到的数据就是修改之后最新的数据
useEffect(() => { console.log(state) }, [state]);

函数组件的setState函数不存在这么一个可以拿到最新state的回调函数,不过我们可以使用useEffect来实现相同的效果。