添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

类组件是一种面向对象思想的体现,类组件之间的状态会随着功能增强而变得越来越臃肿,代码维护成本也比较高,而且不利于后期 tree shaking。所以有必要做出一套函数组件代替类组件的方案,于是 Hooks 也就理所当然的诞生了。

hooks诞生的原因

  • 组件之间的状态复用困难
  • 类组件理解成本较高,业务逻辑分散在生命周期中
  • class和this的特性较为复杂
  • 解决函数组件无状态只能用于简单组件的痛点,使其拥有持续化的状态
  • 拥抱函数式编程
  • 使用hook注意事项

  • 只在只在最顶层使用Hook
  • 不要在循环, 条件或嵌套函数中调用 Hook
  • 常用hooks

    1. useState

    解决函数组件无状态只能用于简单组件的痛点,使其拥有持续化的状态。

    const [value, setValue] = useState(initValue);
    setValue(newValue);
    setValue((value) => newValue);
    
  • 可以在同一个函数组件中被调用多次
  • 在hook中,state状态值不再要求一定是一个对象,可以是数字等简单类型
  • state的类型是对象时,不会自动合并
  • 无回调函数
  • 多个不同的state,为了更好的复用,建议使用多个useState
  • 2. useEffect

    副作用函数,在依赖的数据发生变化时执行,可用来监听状态变化,模拟生命周期等

    useEffect(() => {
        // 依赖发生变化了...
        return () => {
            // 下一次useEffect运行前被执行
    }, [依赖的状态]);
    
  • useEffect可以在同一个函数组件中被调用多次
  • 第一个参数是函数,返回值函数在下一次执行前执行
  • 第二个参数是数组,可以指定绑定的关联数据,不传每次更新都会执行,为空数组则只执行一次
  • useEffect不能被打断
  • useEffect不能被判断包裹
  • 可用useEffect模拟生命周期
  • 模拟生命周期

    useEffect(() => {
        // componentsWillUpdate
        // componentMounted (仅在依赖数组为空时)
        return () => {
            // componentWillUnmount (仅在依赖数组为空时)
            // 这里常用来解绑事件,取消订阅
    }, [依赖的状态]);
    
  • useEffect 里面使用到的state的值, 固定在了useEffect内部,不会被改变,除非useEffect刷新,重新固定state的值。在依赖中添加count即可触发刷新
  •  const [count, setCount] = useState(0)
    useEffect(() => {
        console.log('use effect...',count)
        const timer = setInterval(() => {
            console.log('timer...count:', count) // 一直是
            setCount(count + 1)
        }, 1000)
        return ()=> clearInterval(timer)
    },[])
    

    3. useRef

    希望可以实时拿到最新的值,并且不需要添加到依赖不会触发useEffect重新执行,可用来获取dom等。

    useEffect 里面使用到的state的值, 固定在了useEffect内部, 不会被改变,除非useEffect刷新,重新固定state的值

    import React, { useRef, forwardRef, useImperativeHandle } from 'react' // 转发透传ref const ForwardRefIndex = React.forwardRef((props, ref) => <Input {...props} ref={ref} />) function Home(){ const ref = useRef(null) useEffect(() => { console.log(ref.current) }, []) return <Input ref={ref} />// 父组件调用子函数组件的方法 const JMInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus() return <input type="text" ref={inputRef} />; function ImperativeHandleDemo() { const inputRef = useRef(); return ( <button onClick={() => inputRef.current.focus()}>聚焦</button> <JMInput ref={inputRef} /> </div>

    4. useContext

    解决通用状态在所有子组件中共享的问题,可以在所有子孙组件中使用状态

    const ThemeContext = React.createContext(null)
    // 消费者
    const Son = () => {
      const { color, background } = useContext(ThemeContext);
      return <div style={{ color, background, padding: '20px' }}>这是子组件</div>;
    // 提供者
    const ThemeProvider = ThemeContext.Provider;
    export default function ProviderDemo(){
        const [ contextValue , setContextValue ] = React.useState({  color:'#ccc', background:'pink' })
        return <div>
            <ThemeProvider value={ contextValue } > 
                <Son />
            </ThemeProvider>
        </div>
    

    5. useMemo

    使用缓存避免数据并未更新但是重新render减少渲染

    const Child = memo(({data}) =>{
        console.log('child render...', data.name)
        return (
                <div>child</div>
                <div>{data.name}</div>
            </div>
    const Hook = ()=>{
        console.log('Hook render...')
        const [count, setCount] = useState(0)
        const [name, setName] = useState('rose')
        const data = { name }
        return(
                <div>{count}</div>
                <button onClick={()=>setCount(count+1)}>update count </button>
                <Child data={data}/>
            </div>
    

    当点击按钮更新count的时候,Effect组件会render,执行到const data = {name}这一行代码会生成有新的内存地址的对象,那么就算带着memo的Child组件,也会跟着重新render, 尽管最后其实Child使用到的值没有改变,重复render。

    使用useMemo进行缓存,仅在name更新时再重新触发子组件渲染。

    const data = useMemo(()=>{
       return { name };
    },[name])
    

    6. useCallback

    useCallback 解决了函数未发生变化触发子组件更新的问题,

  • useMemo 是缓存值的
  • useCallback 是缓存函数的
  • const Hook = ()=>{ console.log('Hook render...') const [count, setCount] = useState(0) const [name, setName] = useState('rose') const data = { name } const handleChange = (name) => setName(name); return( <div>{count}</div> <button onClick={()=>setCount(count+1)}>update count </button> <Child data={data} onChange={handleChange}/> </div>

    7. useReducer

    让函数式组件可以像类组件一样集中式管理状态。

  • 可以使用reducer的场景:
  • 如果你的state是一个数组或者对象
  • 如果你的state变化很复杂,经常一个操作需要修改很多state
  • 如果你希望构建自动化测试用例来保证程序的稳定性
  • 如果你需要在深层子组件里面去修改一些状态(关于这点我们下篇文章会详细介绍)
  • 如果你用应用程序比较大,希望UI和业务能够分开维护
  • const initState = {
      name: '',
      pwd: '',
      isLoading: false,
      error: '',
      isLoggedIn: false,
    function loginReducer(state, action) {
      switch(action.type) {
          case 'login':
              return {
                  ...state,
                  isLoading: true,
                  error: '',
          case 'success':
              return {
                  ...state,
                  isLoggedIn: true,
                  isLoading: false,
          case 'error':
              return {
                  ...state,
                  error: action.payload.error,
                  name: '',
                  pwd: '',
                  isLoading: false,
          default: 
              return state;
    function LoginPage() {
      const [state, dispatch] = useReducer(loginReducer, initState);
      const { name, pwd, isLoading, error, isLoggedIn } = state;
      const login = (event) => {
          event.preventDefault();
          dispatch({ type: 'login' });
          login({ name, pwd })
              .then(() => {
                  dispatch({ type: 'success' });
              .catch((error) => {
                  dispatch({
                      type: 'error'
                      payload: { error: error.message }
      return ( 
          //  返回页面JSX Element
    

    自定义hook

    使用react自带的基本hooks封装在业务中可复用的逻辑。

  • 使用useXXX 的命名规范
  • 遵循Hooks规则
  • 1. useDebounceState:节流修改状态

    import { useState, useMemo } from 'react';
    export function debounce(fn, time
    
    
    
    
        
    ) {
      let timer: any = null;
      return (...arg) => {
        if (timer) {
          clearTimeout(timer);
        timer = setTimeout(() => {
          fn.apply(this, arg);
        }, time);
    export function useDebounceState(defaultValue, time = 300) {
      const [value, setValue] = useState(defaultValue);
      const handleChange = useMemo(() => debounce(setValue, time), [time]);
      return [value, handleChange];
    // 使用
    export default function Index(){
        const [ value , setValue ] = useDebounceState('', 300)
        return <div>
          {value}
          <input value={value} onChange={(e)=>setValue(e.target.value)}  />
        </div>
    

    2. useUserInfo:userId变化自动获取用户信息

    import { useEffect, useState } from 'react';
    const sleep = (time = 0) =>
      new Promise((resolve: any) => setTimeout(() => resolve(), time));
    const fetchUserInfo = async (id: string) => {
      await sleep(400);
      return {
        name: 'alan',
        age: 18,
    export const useUserInfo = (id: string) => {
      const [userInfo, setUserInfo] = useState(null as any);
      useEffect(() => {
        async function run() {
          console.log('run');
          const info = await fetchUserInfo(id);
          setUserInfo(info);
        run();
      }, [id]);
      return userInfo;
    复制代码
    分类:
    前端
  •