函数组件有了 Hooks 所向披靡

175 阅读8分钟

一、何为 Hooks

众所周知,函数组件没有状态,这种情况下,Hooks 应运而生。

所谓Hooks,即钩子、钓钩、钩住。顾名思义, 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的以 use 开头的函数。是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

作用: 为函数组件提供状态、生命周期、获取 DOM、传值等原本 在 类 组件中才提供的功能。Hooks 只能在函数组件中使用,自此,函数组件成为 React 的新宠儿。

如此看来, 我们一定要认识一下 Hooks 了。

二、使用 Hooks

1.useState 为函数组件提供状态

示例1:基本使用

// 1.导入 useState 
import React, { useState } from 'react'

const FunUseState = () => {
    // 2.定义初始值,返回值是一个数组
    const countArray = useState(0)
    // 3.获取状态值 count
    const count = countArray[0]
    // 5.定义修改状态值的函数
    const setCount = countArray[1]
    return <div>
        编程不仅仅是技术,还是艺术!FunUseState
        {/* 4.展示状态值 */}
        <p>数量:{count}</p>
        {/* 6.点击按钮,让状态值+20 */}
        <button onClick={() => setCount(count + 20)}>按钮</button>
    </div>
}
export default FunUseState

示例2:使用数组解构优化代码

// 1.导入 useState 这个 hook
import React, { useState } from 'react'

const FunUseState = () => {
    // 2.定义状态值 和修改状态值的函数
    const [count, setCount] = useState(2)
    // 4.注册点击事件修改状态值
    const addHadler = () => {
        setCount(count + 4)
    }
    return <div>
        编程不仅仅是技术,还是艺术!FunUseState
        {/* 3.展示状态值 */}
        <p>数量:{count}</p>
        {/* 5.点击按钮,调用修改状态值的函数 */}
        <button onClick={addHadler}>按钮</button>
    </div>
}
export default FunUseState

示例3:修改状态值的函数可以写成箭头函数

import React, { useState } from 'react'

const FunUseState = () => {
    const [msg, setMsg] = useState('你过来呀')
    const changeMsgHadler = () => {
        setMsg(() => {
            return '嘿嘿'+msg
        })
    }
    return <div>
        编程不仅仅是技术,还是艺术!FunUseState
        <p>打招呼啊:{msg}</p>
        <button onClick={changeMsgHadler}>按钮</button>
    </div>
}
export default FunUseState

示例4:useState 可以写成箭头函数

import React, { useState } from 'react'

const FunUseState = () => {
    const [obj, setObj] = useState(() => {
        return { name: 'Lucy' }
    })
    const changeNameHadler = () => {
        setObj(() => {
            return { name: 'Jane' }
        })
    }
    return <div>
        编程不仅仅是技术,还是艺术!FunUseState
        <p>名字{obj.name}</p>
        <button onClick={changeNameHadler}>按钮</button>
    </div>
}
export default FunUseState

示例5:同一个状态值,修改多次

import React, { useState } from 'react'

const FunUseState = () => {
    // const [count, setCount] = useState(10)
    const [count, setCount] = useState(() => 10)
    const changeCountHandler = () => {
        // ◆第一种:走+10
        // setCount(count+3)
        // setCount(count+10)

        // ◆第二种:同时 加 13 
        // setCount(count + 3)
        // setCount((count) => {
        //     return count + 10
        // })

        // ◆第三种:同时 加 13 
        setCount((count) => {
            return count + 3
        })
        setCount((count) => {
            return count + 10
        })
    }
    return <div>
        编程不仅仅是技术,还是艺术!FunUseState
        <p>数量:{count}</p>
        <button onClick={changeCountHandler}>按钮</button>
    </div>
}
export default FunUseState

示例6:获取表单元素的值

import React, { useState } from 'react'

const FunUseState = () => {
    const [val, setVal] = useState('123')
    const changeValHandler = (e) => {
        setVal(e.target.value)
    }
    return <div>
        编程不仅仅是技术,还是艺术!FunUseState
        <p>表单值:{val}</p>
        <input type="text" value={val} onChange={changeValHandler} />
    </div>
}
export default FunUseState

示例7:实现一个倒计时获取验证码的小功能

import React, { useState } from 'react'

const FunUseState = () => {
     // ◆4.倒计时优化
     const [countNum,setcountNum]=useState(0)
     // 节流优化
     const [flag,setFlag]=useState(false)
     const timeHandler=()=>{
         if(flag) return
         setFlag(true)
         setcountNum(5)
      let timeId=  setInterval(()=>{
             setcountNum((countNum)=>{
                 if(countNum<=0) {
                     clearInterval(timeId)
                     setFlag(false)
                 }
                 return countNum-1
             })
         },1000)
     }
    return <div>
        编程不仅仅是技术,还是艺术!FunUseState
        <br />
        <input type="button" value={countNum<=0?'获取验证码':countNum+'s后获取验证码'} onClick={timeHandler}/>
    </div>
}
export default FunUseState

2、useEffect 为函数组件提供副作用(类似生命周期特性)

useEffect(参数1,参数2)

  • 参数1是箭头函数
    • 里面再写return ()=>{} :监测到组件卸载, 相当于 componentWillUnmount
  • 参数2:
    • 不写 :第一次渲染时会执,每次组件重新渲染会再次执行,相当于 componentDidUpdate
    • [] : 只有组件第一次渲染时执行,相当于 componentDidMount
    • ['状态值'] : 指定状态值的组件第一次渲染时会执行,当该状态值变化时会再次执行,相当于 componentWillUpdate

示例1:useEffect(()=>{}),状态值发生变化

import React, { useState, useEffect } from 'react'

const FunUseEffect = () => {
    const [num, setNum] = useState(10)
    useEffect(() => {
        console.log('useEffect 执行副作用了')
    })
    return <div>
        编程不仅仅是技术,还是艺术!FunUseEffect
        <br />
        <p>数字:{num}</p>
        <button onClick={() => { setNum(20) }}>按钮</button>
    </div>
}
export default FunUseEffect

第 2 个参数不写,初次进入会触发,点击又会触发

示例2:useEffect(()=>{}),状态值没有发生变化

const FunUseEffect = () => {
    const [num, setNum] = useState(10)
    useEffect(() => {
        console.log('useEffect 执行副作用了')
    })
    return <div>
        编程不仅仅是技术,还是艺术!FunUseEffect
        <br />
        <p>数字:{num}</p>
        <button onClick={() => { setNum(10) }}>按钮</button>
    </div>
}
export default FunUseEffect

第 2 个参数不写,初次进入会触发,点击值没有发现变化不会再触发

示例3:useEffect(()=>{},[]),状态值发生变化

import React, { useState, useEffect } from 'react'

const FunUseEffect = () => {
    const [num, setNum] = useState(10)
    useEffect(() => {
        console.log('useEffect 执行副作用了')
    },[])
    return <div>
        编程不仅仅是技术,还是艺术!FunUseEffect
        <br />
        <p>数字:{num}</p>
        <button onClick={() => { setNum(20) }}>按钮</button>
    </div>
}
export default FunUseEffect

第 2 个参数写 [],初次进入会触发,点击不会再触发

示例4:useEffect(()=>{},['count']),状态值发生变化

import React, { useState, useEffect } from 'react'

const FunUseEffect = () => {
    const [num, setNum] = useState(10)
    const [count, setCount] = useState(10)
    useEffect(() => {
        console.log('useEffect 执行副作用了')
    },[count])
    return <div>
        编程不仅仅是技术,还是艺术!FunUseEffect
        <br />
        <p>数字:{num}  数量:{count}</p>
        <button onClick={() => { setNum(20) }}>按钮num</button>
        <button onClick={() => { setCount(count+1) }}>按钮conut</button>
    </div>
}
export default FunUseEffect

第 2 个参数写 [状态值],只有指定的状态值才会触发,其他不触发

示例5:参数 1 里面写return ()=>{} : 相当于 componentWillUnmount

import React, { useState, useEffect } from 'react'

function Func() {
    useEffect(() => {
        console.log('子组件的副作用函数')
        return () => {
            console.log('子组件副作用函数的清理函数');
        }
    }, [])
    return <div style={{ backgroundColor: 'skyblue' }}>我是子组件</div>
}

const FunUseEffect = () => {
    const [flag, setFlag] = useState(true)
    return <div>
        编程不仅仅是技术,还是艺术!FunUseEffect
        <br />
        <button onClick={() => { setFlag(!flag) }}>{flag ? '点击销毁组件' : '点击重建组件'}</button>
        <br />
        {flag && <Func></Func>}
    </div>
}
export default FunUseEffect

useEffect 的返回值 可以监测到组件的销毁

3.useRef 用来获取 DOM

作用:返回一个带有 current 属性的可变对象,通过该对象就可以进行 DOM 操作了。

  • 参数:在获取 DOM 时,一般都设置为 null
  • 返回值:包含 current 属性的对象。
  • 注意:只要在 React 中进行 DOM 操作,都可以通过 useRef Hook 来获取 DOM(比如,获取 DOM 的宽高等)。
  • 注意:useRef不仅仅可以用于操作DOM,还可以操作组件

示例1:获取表单元素的值

import React, { useRef } from 'react'

const FunUseRef = () => {
    const txtRef = useRef(null)
    const checkRef = useRef(null)
    const clickHandler = () => {
        console.log(txtRef.current.value)
        console.log(checkRef.current.checked)
    }
    return <div>
        编程不仅仅是技术,还是艺术!
        <br />
        <input ref={txtRef} />
        <input type="checkbox" ref={checkRef} />敲代码
        <br />
        <button onClick={clickHandler}>点击,获取input中的值</button>
    </div>
}
export default FunUseRef

示例2:获取子组件的值

import React, { useRef } from 'react'

class Func extends React.Component {
    state = {
        msg: '你好呀!'
    }
    render() {
        return <div>
            编程不仅仅是技术,还是艺术!{this.state.msg}
        </div>
    }
}
const FunUseRef = () => {
    const funcRef = useRef(null)
    const clickHandler = () => {
        console.log(funcRef.current.state.msg)
    }
    return <div>
        编程不仅仅是技术,还是艺术!
        <br />
        <button onClick={clickHandler}>点击,获取子组件中的值</button>
        <br />
        <Func ref={funcRef}></Func>
    </div>
}
export default FunUseRef

4.useContext为函数组件提供全局状态

作用:传值,父传子、父传孙

步骤:

  • 导入并调用 createContext 方法,得到 Context 对象,导出
  • 使用 Provider 组件包裹根组件,并通过 value 属性提供要共享的数据
  • 在任意后代组件中,导入useContext,调用 useContext (第一步中导出的context) 得到 value的值
  • 根组件的值发生变化,任意后代组件所获取的值也跟着发生变化

1.项目初始化

  • npx create-react-app react-usecontext
  • cd react-usecontext
  • yarn start 或 npm start 或 npm run start
  • 在 src/useContext 下,新建 FatherFun.jsSonFun.jsGrandSonFun.js image.png

2.App.js 放根组件

import React from 'react'
// 导入 FatherFun 根组件
 import FatherFun from './useContext/FatherFun'
class App extends React.Component {
  com
  render() {
    return <div>
      <FatherFun></FatherFun>
    </div>
  }
}
export default App

3.index.js 入口

import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)

4.父组件 FatherFun.js

// FatherFun 组件 传值
// ◆第一步 :导入 useState  createContext
import React, { useState, createContext } from 'react'
//  使用 useContext 实现父传子子孙孙
// 导入 子组件 SonFun
import SonFun from './SonFun'
// 第二步:把 定义 的  Context 暴露出去
export const Context = createContext()
const FatherFun = () => {
    // 第四步:用 useState 提供状态,准备要传的值,定义初始值
    // ◆ 新增需求:父组件改值,子子孙孙组件收到的值也随着改变
    const [msg, setMsg] = useState('常回家看看')
    const changeMsgHandler = () => {
        setMsg('回家吃饺子呀')
    }
    // 第三步:用<Context.Provider></Context.Provider> 包裹子孙组件
    // 第五步:通过 value 传递值给子孙组件
    return (<Context.Provider value={msg}><div>
        编程不仅仅是技术,还是艺术!GrandPa
        <br />
        <button onClick={changeMsgHandler}>改值</button>
        <hr />
        <SonFun></SonFun>
    </div></Context.Provider>)
}
export default FatherFun

5.子组件 SonFun.js

// SonFun 组件 接收
// 第一步:导入 useContext 
import React, { useContext } from 'react'
// 导入 子组件 GrandSonFun
import GrandSonFun from './GrandSonFun'
// 第二步:导入 Context 
import { Context } from './FatherFun'

const SonFun = () => {
    // 第三步:定义变量接收 值
    const msg = useContext(Context)
    // 第四步:处理数据,显示在页面等
    return <div>
        编程不仅仅是技术,还是艺术!SonFun
        <p>FatherFun 传过来的东西:{msg}</p>
        <hr />
        <GrandSonFun></GrandSonFun>
    </div>
}
export default SonFun

6.孙组件 GrandSonFun.js

// GrandSonFun 组件 接收
// 第一步:导入 useContext
import React, { useContext } from 'react'
//  第二步:导入 Context
import { Context } from './FatherFun'
const GrandSonFun = () => {
    // 第三步:定义变量接收
    const msg = useContext(Context)
    // 第四步:处理数据,展示在页面等
    return <div>
        编程不仅仅是技术,还是艺术!GrandSonFun
        <p>FatherFun 传递过来的东西:{msg}</p>
    </div>
};
export default GrandSonFun

image.png

三、Hooks 的优势

体验了几把 Hooks,相信你已经被它折服,为此惊叹。

下面,我们总结一下 Hooks 的优势:

  • 组件开发模式更灵活

    • React v16.8 以前,组件开发模式: class 组件(提供状态) + 函数组件(展示内容)
    • React v16.8 及其以后:
      • class 组件(提供状态) + 函数组件(展示内容)
      • Hooks(提供状态) + 函数组件(展示内容)
      • 混用以上两种方式:部分功能用 class 组件,部分功能用 Hooks+函数组件
  • Hooks 只能在函数组件中使用,避免了 class 组件的问题

  • 复用组件状态逻辑,而无需更改组件层次结构

  • 具有更好的 TS 类型推导

  • tree- - shaking 友 好,打包时去掉未引用的代码

  • 更好的压 缩

  • 可以自定义自己的专属 Hooks