一、何为 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 操作,都可以通过
useRefHook 来获取 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.js、SonFun.js、GrandSonFun.js
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
三、Hooks 的优势
体验了几把 Hooks,相信你已经被它折服,为此惊叹。
下面,我们总结一下 Hooks 的优势:
-
组件开发模式更灵活
- React v16.8 以前,组件开发模式: class 组件(提供状态) + 函数组件(展示内容)
- React v16.8 及其以后:
- class 组件(提供状态) + 函数组件(展示内容)
- Hooks(提供状态) + 函数组件(展示内容)
- 混用以上两种方式:部分功能用 class 组件,部分功能用 Hooks+函数组件
-
Hooks只能在函数组件中使用,避免了class 组件的问题 -
复用组件状态逻辑,而无需更改组件层次结构
-
具有更好的
TS类型推导 -
tree- - shaking友 好,打包时去掉未引用的代码 -
更好的压 缩
-
可以自定义自己的专属
Hooks