React Hooks系列之useState

react hooks 是 React 16.8 的新增特性。 它可以让我们在函数组件中使用 state 、生命周期以及其他 react特性,而不仅限于 class 组件。react hooks 的出现,标示着 react中不会在存在无状态组件了,只有类组件和函数组件。具体可查看官网

优势:

  1. 函数组件不能使用state,遇到交互更改状态等复杂逻辑时不能更好地支持,hooks让函数组件更靠近class组件,拥抱函数式编程。
  2. 解决副作⽤问题,hooks出现可以处理数据获取、订阅、定时执行任务、手动修改 ReactDOM这些⾏为副作用,进行副作用逻辑。比如useEffect。
  3. 更好写出有状态的逻辑重用组件。
  4. 让复杂逻辑简单化,比如状态管理:useReducer、useContext。
  5. 函数式组件比class组件简洁,开发的体验更好,效率更⾼,性能更好。
  6. 更容易发现无用的状态和函数。

    useState介绍

    const [state, setState] = useState(initialState);
    

    返回一个 state,以及更新 state 的函数setState。

    • state是要设置的状态 。
    • setState是更新state的方法。setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。
    • initState是初始的state,可以是随意的数据类型,也可以是回调函数,但是函数必须是有返回值(惰性state)。

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

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

    在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。

    setState(newState);
    //这里的值不会立即变化
    

    使用useState钩子提供的更新程序的状态更新也是异步的,并且不会立即反映和更新,但会触发重新呈现。在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。

    useState原理

    我们需要写一个 useState 方法,会返回当前状态和设置状态的方法,每当状态改变之后,方法中会调用刷新视图的 render 方法。状态我们需要放在最外面,方便下次执行函数时可以重新取值。初始状态只会在函数第一次执行的时候赋值。

    let memoizedState;
    function useState (initialState) {
      memoizedState = memoizedState || initialState
      function setState (newState) {
        memoizedState = newState
        render()
      }
      return [memoizedState, setState]
    }
    

    这样确实是可以正常使用,但是当多个 useState 存在的时候就有问题了,只能变一个状态了。现在我们需要优化我们的 Hooks ,解决多个 useState 同时使用的问题,当多个状态存在的时候,我们需要使用数组保存状态。

    let memoizedStates = []
    let index = 0
    function useState (initialState) {
      memoizedStates[index] = memoizedStates[index] || initialState
      let currentIndex = index
      function setState (newState) {
        memoizedStates[currentIndex] = newState
        render()
      }
      return [memoizedStates[index++], setState]
    }
    

    useState使用

    例子:

     function App () {
        const [ count, setCount ] = useState(0)
        return (
          <div>
            点击次数: { count }
            <button onClick={() => { setCount(count + 1)}}>点我</button>
          </div>
          )
      }
    

    相同值,当我们在使用 useState 时,修改值时传入同样的值,我们的组件是不会渲染的。

      function App () {
        const [ count, setCount ] = useState(0)
        console.log('我就看你渲染不')
        return (
          <div>
            点击次数: { count }
            <button onClick={() => { setCount(count)}}>点我</button>
          </div>
          )
      }
    

    useState注意点

    1. useState最好写到函数的起始位置, 主要是便于阅读
    2. useState严禁出现在代码块(判断和循环等)中
    3. useState返回的函数(数组的第二项), 这个函数的引用是不会变化的(优化性能)
    4. 如果使用函数改变数据, 若数据和之前的数据完全相等(使用Object.is), 则不会重新渲染, 由于Object.is是浅比较, 所以如果状态是一个对象的时候要小心操作了
    5. 如果使用函数改变数据, 传入的值不会和原来的数据进行合并而是直接进行替换(跟setState完全不一样), 所以在修改对象的时候, 我们要先将之前的对象保存下来
    6. 不要直接去改变state的值
    7. 果要实现强制刷新组件的情况: 如果是类组件我们都会使用forceUpdate, 在函数组件中, 我们可以用useState来实现, 使用useState的改变state的函数传入一个空对象, 因为每次传入一个空对象的地址不一样所以一定会刷新。使用const [, forceUpdate] = useState({});forceUpdate({})
    8. 和类组件一样, 函数组件的状态更改在某些时候是一步的(dom事件下), 如果是异步的更改, 则多个状态的更改会合并, 此时不能信任之前的状态, 而应该使用回调函数的方式改变状态
    9. 如果某些状态之间没有必然的联系, 应该分化为不同的状态而非合并成一个对象。

    useState更新延迟问题

    看过上面的解释,相信大家知道这里要说了什么了

    const [current, setCurrent] = useState(0)
      const click = () => {
         setCurrent(current+ 1)
         console.log(current)  //0
      }
    

    解决方案:

    • 用useRef代替(类似沙箱,它还可以“跨渲染周期”保存数据)
    • 直接用变量存储或用函数包裹(组件渲染重新刷新变量)
    • 配合useEffect处理

    具体情况具体处理

     const current = useRef(0);
      const click = () => {
         current.current += 1
         console.log(current.current) //1
      }
    
      const [name, setName] = useState('1');
      const handleTest = () => {
        console.log(name) //1
        setName('2')
        console.log(name) //1
      }
    
    
    useEffect(() => {
    
    console.log(name) //2
    
    },[name])
    
posted @ 2022-04-14 11:28  Tommy_marc  阅读(260)  评论(0编辑  收藏  举报