react-hooks学习记录

142 阅读5分钟

原文地址

用动画和实战打开 React Hooks(一):useState 和 useEffect

用动画和实战打开 React Hooks(二):自定义 Hook 和 useCallback

# 用动画和实战打开 React Hooks(三):useReducer 和 useContext

在 Hooks 出现之前,类组件和函数组件的分工一般是这样的:

  • 类组件提供了完整的状态管理和生命周期控制,通常用来承接复杂的业务逻辑,被称为“聪明组件”
  • 函数组件则是纯粹的从数据到视图的映射,对状态毫无感知,因此通常被称为“傻瓜组件”

Hooks的出现,解决了函数组件没有状态

理解函数式组件的运行过程

函数式组件运行过程

当我们第一次调用组件函数时,触发初次渲染;然后随着 props 的改变,便会重新调用该组件函数,触发重渲染

useState 使用浅析

setState渲染动画

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

useState这个钩子为函数式组件提供了状态管理,通过调用setState函数,可以触发组件的重渲染。每次渲染具有独立的状态值(毕竟每次渲染都是完全独立的嘛)。也就是说,每个函数中的 state 变量只是一个简单的常量,每次渲染时从钩子中获取到的常量,并没有附着数据绑定之类的神奇魔法。

function Counter() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert('You clicked on: ' + count);
    }, 3000);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      <button onClick={handleAlertClick}>
        Show alert
      </button>
    </div>
  );
}
  • 每次渲染相互独立,因此每次渲染时组件中的状态、事件处理函数等等都是独立的,或者说只属于所在的那一次渲染

  • 我们在 count 为 3 的时候触发了 handleAlertClick 函数,这个函数所记住的 count 也为 3

  • 三秒种后,刚才函数的 setTimeout 结束,输出当时记住的结果:3

useEffect 使用浅析

useEffect(effectFn, deps)

1.useEffect 的第一个参数**effectFn参数** 是一个执行某些可能具有副作用的 Effect 函数(例如数据获取、设置/销毁定时器等),它可以返回一个清理函数(Cleanup),例如大家所熟悉的 setIntervalclearInterval

useEffect(() => {
  const intervalId = setInterval(doSomething(), 1000);
  return () => clearInterval(intervalId);
});

2.useEffect 的第二个参数deps(依赖数组), 依赖数组就是用来控制是否应该触发 Effect,从而能够减少不必要的计算,从而优化了性能。具体而言,只要依赖数组中的每一项与上一次渲染相比都没有改变,那么就跳过本次 Effect 的执行。

  1. 初次渲染componentDidMountdeps为空数组

    useEffect(() => {
    	// 组件加载完成执行
      
      return () => {
        // 返回一个函数,在组件卸载时执行
      }
    }, [])
    
  2. 重渲染componentDidUpdatedeps传入需要监听的state

    function App() {
    	const [count, setCount] = useState(0);
      useEffect(() => {
    		console.log('count 更新后' + count);
      }, [count]);
      
      const handleClick = () => {
        setCount(count++);
      };
      return (
      	<div>
        	<button onclick={handleClick}>Click me</button>
        </div>
      )
    }
    

useLayoutEffect钩子在渲染之前执行,使用方法与 useEffect 完全一致,只是执行的时机不同。

React 官方文档 Rules of Hooks 中强调过一点:Only call hooks at the top level. 只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook,确保总是在你的 React 函数的最顶层调用他们:

useEffect(() => {
  if() {
    ...
  }
})

一个简单的自定义Hook

function useCustomHook() {
  const [scrollPosition, setScrollPosition] = useState(null);

  useEffect(() => {
    const handleScroll = () => setScrollPosition(window.scrollY);
    document.addEventListener('scroll', handleScroll);
    return () =>
      document.removeEventListener('scroll', handleScroll);
  }, []);

  return scrollPosition;
}

const Custom = () => {
  const scroll = useCustomHook();
  console.log(scroll)
  return (
    <div style={{minHeight:800}}>

    </div>
  )
}

一个名为useCustomHook的函数,但不是React函数组件,内部通过使用 React 自带的一些 Hook (例如 useStateuseEffect )来实现某些通用的逻辑。

这里推荐两个强大的 React Hooks 库:React UseUmi Hooks。它们都实现了很多生产级别的自定义 Hook,非常值得学习。

useCallback

const memoizedCallback = useCallback(callback, deps);

第一个参数 callback 就是需要记忆的函数,第二个参数就是大家熟悉的 deps 参数,同样也是一个依赖数组(有时候也被称为输入 inputs)。

在大多数情况下,我们都是传入空数组 [] 作为 deps 参数,这样 useCallback 返回的就始终是同一个函数,永远不会更新

useMemo

实际上,useMemo 的功能是 useCallback超集。与 useCallback 只能缓存函数相比,useMemo 可以缓存任何类型的值(当然也包括函数)。useMemo 的使用方法如下:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

其中第一个参数是一个函数,这个函数返回值的返回值(也就是上面 computeExpensiveValue 的结果)将返回给 memoizedValue 。因此以下两个钩子的使用是完全等价的:

useCallback(fn, deps);
useMemo(() => fn, deps);

useContext

useContext可以使组件之间共享状态

const AppContext = createContext();

const Child1 = () => {
  const { name } = useContext(AppContext)
  return (
    <div>
      <h1>子组件1: {name}</h1>
    </div>
  )
}
const Child2 = () => {
  const { name } = useContext(AppContext)

  return (
    <div className="messages">
      <h1>子组件2: {name}</h1>
    </div>
  )
}

const Father = () => {
  return (
    <AppContext.Provider value={{
      name: 'superawesome'
    }}>
      <Child1 />
      <Child2 />
    </AppContext.Provider>
  )
}

useReducer

const [state, dispatch] = useReducer(reducer, initialState);

上面是useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。

下面是一个计数器的例子。用于计算状态的 Reducer 函数如下。

const myReducer = (state, action) => {
  switch(action.type)  {
    case('countUp'):
      return  {
        ...state,
        count: state.count + 1
      }
    case('countDown'):
      return {
        ...state,
        count: state.count - 1
      }
    default:
      return  state;
  }
}
function App() {
  const [state, dispatch] = useReducer(myReducer, { count:   0 });
  return  (
    <div className="App">
      <button onClick={() => dispatch({ type: 'countUp' })}>+1</button>
      <button onClick={() => dispatch({ type: 'countDown' })}>+1</button>
      <p>Count: {state.count}</p>
    </div>
  );
}

useContextuseReducer配合使用,实现一个轻量级的、类似 Redux 的状态管理模型。

useRef

适用场景:

  1. 获取子组件的实例,只有类组件可以使用

    function Example(){
      const inputEl=useRef(null)
        
      function handleClick(){
         console.log(inputEl)
         inputEl.current.focus()
      }
    
      return (
        <>
        <input type="text" ref={inputEl}/>
        <button onClick={handleClick}>点击</button>
        </>
      )
    }
    
  2. 在函数组件中定义一个全局变量,不会因为重复render重复声明,类似于组件的this.xxx

    const App = () => {
      const [count, setCount] = useState(0);
      const latestCount = useRef();
    
      useEffect(() => {
        latestCount.current = count
      })
    
      const handleAlertClick = () => {
        setTimeout(() => {
          alert(latestCount.current)
        }, 3000);
      }
    
      return (
        <div>
          <p>count:{count} </p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
          &nbsp;
          <button onClick={handleAlertClick}>
            Show alert
          </button>
        </div>
      )
    }