React Hook 是 React 16.8 的新特性

  • 让函数组件能彻底取代 class 组件
  • 干掉 state、声明周期、this 绑定

    一、useState 用法

调用 useState 函数并设置初始值即可

  1. function Counter() {
  2. const [count, setCount] = useState(0);
  3. return (
  4. <div>
  5. <div>{count}</div>
  6. <Button onClick={() => { setCount(count + 1); }}>
  7. 点击
  8. </Button>
  9. </div>
  10. );
  11. }

二、useEffect 用法

第二参数有三种设置方法,并自己设置是否返回一个函数,这个函数会在第二次渲染时执行

  • useEffect 不设置第二个参数 —— 监听所有 state
  • useEffect 设置第二参数 —— 监听数组内的 state
  • useEffect 设置第二参数为空数组 —— 只在第一次挂载时执行

1. 用法一

useEffect 不设置第二个参数

  1. function Counter(){
  2. const [count, setCount] = useState(0)
  3. const [age, setAge] = useState(20);
  4. useEffect(()={
  5. console.log('组件渲染了') // 监控所有 state
  6. })
  7. return (<div>
  8. <div>>{count}</div>
  9. <button onClick={()=>{setCount(count+1)}}>+1</button>
  10. <p>{age}</p>
  11. <button onClick={setAge(age+1)}>+1</button>
  12. </div>)
  13. }

2. 用法二

useEffect 的第二参数设置一个 state,并返回一个函数

  1. function Counter(){
  2. const [count, setCount] = useState(0)
  3. const [age, setAge] = useState(20);
  4. useEffect(()={
  5. console.log('挂载和状态更新时执行')
  6. return ()=>{
  7. console.log('状态更新和卸载组件时执行')
  8. }
  9. },[count]) // 只监控 count 状态
  10. return (<div>
  11. <div>>{count}</div>
  12. <button onClick={()=>{setCount(count+1)}}>+1</button>
  13. <p>{age}</p>
  14. <button onClick={setAge(age+1)}>+1</button>
  15. </div>)
  16. }

3. 用法三

useEffect 的第二参数设置为空数组

  1. impont React,{useState,useEffect} from 'react'
  2. function App(){
  3. const [count, setCount] = useState(0)
  4. useState(()=>{
  5. console.log('首次执行')
  6. return ()=>{
  7. console.log('仅卸载时执行')
  8. }
  9. },[]) // 都不监控,只在第一次挂载时执行一次
  10. return (<div>
  11. <div>>{count}</div>
  12. <button onClick={()=>{setCount(count+1)}}>+1</button>
  13. <p>{age}</p>
  14. <button onClick={setAge(age+1)}>+1</button>
  15. </div>)
  16. }

三、模拟 useState 和 useEffect 接口

1. useState 简单实现

核心功能:state 和 setState

  1. const render = () => {
  2. ReactDOM.render(
  3. <App />,
  4. document.querySelector('#root')
  5. )
  6. }
  7. let state = undefined;
  8. function useState(initialValue) {
  9. if (!state) {
  10. state = initialValue
  11. }
  12. function setState(newState) {
  13. state = newState;
  14. render();
  15. }
  16. return [state, setState];
  17. }

2. useEffect 简单实现

核心功能

  • 第一次渲染组件时执行一次
  • 监控数据变化,在发生变化时执行一次
  • 第二参数为空数组时,只执行一次
  1. let oldDeps = undefined // 记录上一次数组,第一次当然未定义
  2. function useEffect(callback, depArray) {
  3. const hasDeps = depArray
  4. // 空数组永远返回 true
  5. // 有值的数组时,一一对比是否和上一次对比是否一变化,第一次对比一定返回 false,以后有变化时返回 false
  6. // depArray.every((item,i)=>item === oldDeps[i]}
  7. const hasChangedDeps = oldDeps ? !depArray.every((item, i) => item === oldDeps[i]) : true
  8. if (hasChangedDeps || !hasDeps) {
  9. callback()
  10. oldDeps = depArray
  11. }
  12. }

3. 完善

  • 以上 state 只是定义了一个全局变量,无法满足多个 state 的情况
  1. let memoizedState = []; // hooks 存放在这个数组,放 useState 的 state 和 useEffect 的监控数组
  2. let cursor = 0; // 当前 memoizedState 下标
  3. const render = ()=>{
  4. ReactDOM.render(
  5. <App />,
  6. document.querySelector('#root')
  7. )
  8. }
  9. // [100,101]
  10. // 0 1 2
  11. // 每次使用 useState 时,cursor 会指向数组的下一位
  12. // 每个 setState 都会使用之前记录下的 currentCursor
  13. function useState(initialValue) {
  14. memoizedState[cursor] = memoizedState[cursor] || initialValue;
  15. const currentCursor = cursor;
  16. function setState(newState) {
  17. memoizedState[currentCursor] = newState;
  18. render();
  19. }
  20. return [memoizedState[cursor++], setState]; // 返回当前 state,并把 cursor 加 1
  21. }
  22. function useEffect(callback, depArray) {
  23. const hasNoDeps = !depArray;
  24. const deps = memoizedState[cursor];
  25. const hasChangedDeps = deps
  26. ? !depArray.every((el, i) => el === deps[i])
  27. : true;
  28. if (hasNoDeps || hasChangedDeps) {
  29. callback();
  30. memoizedState[cursor] = depArray;
  31. }
  32. cursor++;
  33. }
  • 这是闭包的经典使用
  • 真正的 React 里不是用的数组,而是链表,但是原理类似
  • 难点是每次 render 后,数组要对上号