React Hooks 可以说是 React 最重要 的内容之一。常见的 React Hooks 命名 通常 以 use 开头,比如 useState、useEffect 等。
本文将采用图文详解的方式,带你快速了解:React 内置 Hooks、自定义 Hooks(复用代码)、第三方 Hooks 的使用。
import React, { useState } from 'react'
function App() {
// let count = 0 // 普通的 js 变量,无法触发组件的更新
const [count, setCount] = useState(0) // useState 可以触发组件的更新
function add() {
setCount(count + 1)
console.log(count, 'count')
}
return (
<>
<div>
<button onClick={add}>add {count}</button>
</div>
</>
)
}
export default App
import React, { FC, useState } from 'react'
const StateDemo: FC = () => {
const [count, setCount] = useState(0) // useState 可以触发组件的更新
const [name, setName] = useState('张三')
function add() {
// 写法一:
setCount(count + 1)
// 写法二:
// setCount(count => count + 1)
/** 打印出来的 count 永远是累加之前的 count */
console.log(count, 'cur count') // 异步更新,无法直接拿到最新的 state 值
}
return (
<>
<button onClick={add}>add {count}</button>
</>
)
}
export default StateDemo
import React, { FC, useState } from 'react'
const StateDemo: FC = () => {
const [name, setName] = useState('张三')
function add() {
setName('李四')
console.log(name) // 如果说一个变量不用于 JSX 中显示,那就不要用 setState 来管理它,用 useRef
}
}
export default StateDemo
import React, { FC, useState } from 'react'
const StateDemo: FC = () => {
const [count, setCount] = useState(0) // useState 可以触发组件的更新
function add() {
/** 方法一:看代码觉得可能会实现 count + 5,但由于是异步更新,每次执行的时候,count 任然是0。所以最后仍然是 count + 1 */
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
/** 方法二:可以实现 count + 5 */
setCount(count => count + 1)
setCount(count => count + 1)
setCount(count => count + 1)
setCount(count => count + 1)
setCount(count => count + 1)
console.log(count, 'cur count')
}
return (
<>
<div>
<button onClick={add}>add {count}</button>
</div>
</>
)
}
export default StateDemo
注:编辑 state 的数据只能是传入一个的数据进行覆盖,而不能修改原数据
import React, { FC, useState } from 'react'
import QuestionCard from './components/QuestionCard'
const List2: FC = () => {
// 问卷列表数据
const [questionList, setQuestionList] = useState([
{ id: 'q1', title: '问卷1', isPublished: false },
{ id: 'q2', title: '问卷2', isPublished: true },
{ id: 'q3', title: '问卷3', isPublished: true },
{ id: 'q4', title: '问卷4', isPublished: false },
])
// 新增问卷
function handleAdd() {
// 生成三位随机数
const r = Math.random().toString().slice(-3)
setQuestionList(
questionList.concat({
id: 'q' + r,
title: '问卷' + r,
isPublished: false,
})
)
}
// 删除问卷
function deleteQuestion(id: string) {
setQuestionList(questionList.filter(item => item.id != id))
}
// 编辑问卷
function publishedQuestion(id: string) {
setQuestionList(
questionList.map(item => {
if (item.id !== id) return item
return {
...item,
isPublished: true,
}
})
)
}
}
export default List2
使用 immer 可避免这一个问题
安装 immer
npm install immer --save
使用 immer
import React, { FC, useState } from 'react'
import { produce } from 'immer'
const Demo: FC = () => {
const [userInfo, setUserInfo] = useState({ name: '张三', age: 24 })
// 修改个人信息
const handleChangeUser = () => {
setUserInfo(
// 通过 immer,即可让我们便捷的修改数据
produce(draft => {
draft.name = '李四'
})
)
}
return (
<div>
<div>{JSON.stringify(userInfo)}</div>
<button onClick={handleChangeUser}>change age</button>
</div>
)
}
export default Demo
import React, { FC, useState, useEffect } from 'react'
import { produce } from 'immer'
import QuestionCard from './components/QuestionCard'
const List2: FC = () => {
/** 方式一:直接发起 Ajax 请求。不行,state值改变时会触发请求,造成重复发送冗余请求 */
// console.log('方式一加载 Ajax 网络请求')
/** 方式二:此方法即可 */
useEffect(() => {
console.log('方式二加载 Ajax 网络请求')
}, []) // 第二个参数为依赖项, 数组中可以放多个 state 值,当 state 值改变时,会触发 函数(第一个参数)的执行。若为空,
const [questionList, setQuestionList] = useState([
{ id: 'q1', title: '问卷1', isPublished: false },
{ id: 'q2', title: '问卷2', isPublished: true },
{ id: 'q3', title: '问卷3', isPublished: true },
{ id: 'q4', title: '问卷4', isPublished: false },
])
}
export default List2
组件创建、销毁以及依赖的 state 数据改变时,会执行
import React, { FC, useState, useEffect } from 'react'
import { produce } from 'immer'
import QuestionCard from './components/QuestionCard'
const List2: FC = () => {
/** 方式一:直接发起 Ajax 请求。不行,会频繁的重复请求 */
// console.log('方式一加载 Ajax 网络请求')
/** 方式二:此方法即可 */
useEffect(() => {
// 组件挂载时执行
console.log('component mounted')
return () => {
// 组件销毁时执行
console.log('component unmounted')
}
}, [questionList]) // 如果此数组中依赖数据,则依赖数据更新时会执行
const [questionList, setQuestionList] = useState([
{ id: 'q1', title: '问卷1', isPublished: false },
{ id: 'q2', title: '问卷2', isPublished: true },
])
}
export default List2
示例代码一:操作 DOM
import React, { FC, useRef } from 'react'
const UseRefDemo: FC = () => {
const inputRef = useRef<HTMLInputElement>(null)
function handelSelect() {
const element = inputRef.current // 可以拿到 DOM 节点,进行 DOM 操作
element?.select()
}
return (
<div>
<input ref={inputRef} defaultValue="Hello Word" />
<button onClick={handelSelect}>select button</button>
</div>
)
}
export default UseRefDemo
示例代码二:不会触发 rerender
import React, { FC, useRef } from 'react'
const UseRefDemo: FC = () => {
const name = useRef('张三')
function handleChangeName() {
name.current = '王小虎' // 修改 ref 的值,不会触发 rerender(修改 state 的值,会触发 rerender)
console.log(name.current, 'now name') // 此时页面仍然是 张三,但是此处打印出来的是 王小虎
}
return (
<div>
<div>{name.current}</div>
<button onClick={handleChangeName}>select button</button>
</div>
)
}
export default UseRefDemo
使用示例代码:
import React, { FC, useMemo, useState } from 'react'
const Demo: FC = () => {
const [num1, setNum1] = useState(10)
const [num2, setNum2] = useState(20)
const [test, setTest] = useState('测试') // 更新组件,导致组件 rerender
const sum = useMemo(() => {
return num1 + num2
}, [num1, num2])
return (
<>
<p> {sum} </p>
<p> {num1}{' '}<button onClick={() => { setNum1(num1 + 1) }}>add num1</button></p>
<p> {num2}{' '} <button onClick={() => {setNum2(num2 + 2)}}>add num2</button></p>
<div>
{/* form组件,受控组件 */}
<input value={test} onChange={e => setTest(e.target.value)} />
</div>
</>
)
}
export default Demo
import React, { FC, useState, useCallback } from 'react'
const Demo: FC = () => {
const [text, setText] = useState('Hello')
// fn1 是只要组件重新更新、重新执行,那么 fn1 就会被重新定义 -- 无缓存
const fn1 = () => console.log('执行 fn1 ')
// fn2 用了 useCallback ,就会根据依赖项去重新定义 -- 有缓存
const fn2 = useCallback(() => {
console.log('执行 fn2')
}, [text]) // 依赖项,与 useEffect,useMemo,useCallback 用法类似
// 缓存,为了性能优化,提升时间效率
// 但是不能为了优化,要根据业务而定
return (
<>
<div>
<button onClick={fn1}>fn 1</button> <button onClick={fn2}>fn 2</button>
</div>
{/* form组件,受控组件 */}
<input value={text} onChange={e => setText(e.target.value)} />
</>
)
}
export default Demo
最完美的解决方案
,Vue3也在参考4.3.1 定一个 hooks
// /src/hooks/useTitle.ts
import { useEffect } from 'react'
function useTitle(title: string) {
useEffect(() => {
// 修改网页标题
document.title = title
}, [])
}
export default useTitle
4.3.2 使用 自定义 hooks
import React from 'react'
// 引入 自定的 hooks
import useTitle from './hooks/useTitle'
function App() {
... 其他代码
// 使用自定义的 hooks
useTitle('React 自定义 Hook 学习')
... 其他代码
}
export default App
国内常用
官网地址:https://ahooks.js.org/安装 ahooks
npm install --save ahooks
使用
import React from 'react';
import { useTitle } from 'ahooks';
export default () => {
useTitle('Page Title');
return (
<div>
<p>Set title of the page.</p>
</div>
);
};