react在16.8版本就用了hook,hook是在以前的无状态纯函数组件增加了内部自己的状态,而且我们完全可以用hook方式替换原有的class组件,本文是一篇重读react官网hook的笔记。
在开始本文之间,主要会从以下方面理解hook
为什么会有hook,hook到底有什么好
hook组件比class组件更简单,更容易维护
核心
hookAPI的使用
hook到底有多好
在官网总结里,使用过往class组件其实主要有以下几个弊端
class组件之间
状态难以复用class内部复杂的钩子
componentDidMount,componentDidUpdate,componentWillUnmount耦合了太多的逻辑,在class组件耦合了这样类似的生命周期钩子有太多的逻辑状态,使得组件的复用有很大挑战。class组件不易压缩,而且在热重载上有效果不好
针对以上hook做了一些渐进增强,向后兼容,hook不是唯一选择,不影响原有class组件的生命周期
让组件状态可以更好的复用
不再有那么的
生命周期钩子函数,而是更简单的hook,组件细粒度可以更小的纯函数。
hook是什么
hook本质就是纯函数,通常我们使用hook就是useXXX,我们看下hook的第一个基础hookuseState
useState
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
useState的初始值就是传入的形参initState,并且返回是一个数组[state,dispatch]
我们看一下官网简单的例子
import React, { useState } from "react";
function Title() {
const [count, setCount] = useState(0);
const add = () => {
console.log(count, 'before count')
setCount(count + 1)
}
return (<>
<p>{count}</p>
<button onClick={add}>计数</button>
</>)
}
export default Title
useState初始值是0,然后我们用数组解构方式,第一个值是state,第二值是setState,注意我们setCount是异步,这与class组件的setState是一样的
useEffect
为函数组件提供了副作用能力,怎么理解副作用,就是我们平时获取数据,修改dom之类的操作,一个函数内部产生不确定性操作这就是副作用。
官方解释useEffect是原有componentDidMount,componentDidUpdate,componentWillUnmount的组合
具体我们看下
function Title() {
const [count, setCount] = useState(0);
const add = () => {
console.log(count, 'before count1')
setCount(count + 1);
console.log(count, 'before count2')
}
useEffect(() => {
console.log(count, 'after count title')
document.title = count;
})
const renderText = () => {
console.log(count, 'render')
return count
}
return (<>
<p>{renderText()}</p>
<button onClick={add}>添加</button>
</>)
}

useEffect这个hook是在更新dom后,执行的,也就react会在dom更新后再执行useEffect中的内部的副作用函数,每次更新state,就会重新渲染组件,useEffect会重新调用。
而useEffect内部就是能获取到修改后最新的值。
为啥useEffect能拿到最新值,这是因为这其实用了一个js中的闭包,在useEffect内部有访问函数外部的变量,在Title这个函数组件内部作用域内,useEffect内部是可以访问函数组件的变量的。
消除useEffect副作用
我们知道useEffect有副作用,那么如何消除呢,官网介绍到,只需要返回一个函数即可,在官网提到,如果是外部数据源,是有必要消除副作用的,这样可以防止内存泄漏。
...
useEffect(() => {
console.log(count, 'after count title')
document.title = count;
return () => {
console.log('清除副作用')
}
})
在useEffect(callback, []),如果是这样,就只会调用一次,如果会传入一个值useEffect(callback, [count]),那么count变化了,那么useEffect就会重新调用
我们尝试在useEffect使用一个定时器计数,如下
useEffect(() => {
console.log(count, 'after count title')
document.title = count;
const timer = setInterval(() => {
setCount(count => count + 1);
}, 1000)
return () => {
console.log('清除副作用')
}
})
你会发现定时器计数设值是不准确,这个问题怎么解呢,实际上只需要在useEffect中清除即可
useEffect(() => {
console.log(count, 'after count title')
document.title = count;
prevCount.current = count;
const timer = setInterval(() => {
setCount(count => count + 1);
}, 1000)
return () => {
clearInterval(timer)
console.log('清除副作用')
}
})
在hook上使用有规定
不能使用在条件,循环,子函数使用
只存在于
函数组件与自定义hook中调用hook可以多次重复使用
通俗来讲,就是只能在函数组件最顶层使用
自定义hook
像render props与高阶组件一样共享组件状态逻辑
用一段为伪代码来感受下
// useFeatchList
import {useState, useEffect } from 'react'
import fetchListProxy from '@/service/proxy'
export function useFeatchList(id) {
const [data, setData] = useState([]);
useEffect(async () => {
// 请求获取数据
const {list} = await fetchListProxy(id);
setData(list)
}, []);
return {
data
}
}
在另一个页面
import react from 'react';
import { useFeatchList} from './useFeatchList'
function List (props) {
const {data} = useFeatchList(props.id);
return (<>
<ul>
{
data.map(v => <li>{v.text}</li>)
}
</ul>
</>)
}
function App () {
return <List id="123" />
}
export default App
useFeatchList就是一个自定义hook,我们把请求获取数据成了一个hook,与实际组件解藕了出去,这样业务逻辑更加清晰了
我们再来看下官方的一个自定义自己的redux,redux是react状态管理工具类似vuex一样,但是个人觉得vuex相比较redux理解起来也容易得多。
我们今天并不打算去了使用redux这个库,设计使用还是有些复杂。但是我们可以实现一个类似redux的功能,我们封装一个自定义hook,useReducer
useReducer
import React, { useState, useEffect, createRef } from "react";
import { useReducer } from '../../hook/useReducer';
function addReducer(state, action) {
switch (action.type) {
case 'add':
return [...state, {
...action.payload
}];
default:
return state;
}
}
function Title() {
const [data, dispatch] = useReducer(addReducer, []);
const [val, setValue] = useState('Web技术学苑');
const inputRef = createRef();
const dispatchAdd = () => {
dispatch({
type: 'add',
payload: {
name: inputRef.current.value
}
})
}
const renderData = () => {
return data.map((v, index) => <p key={index}>{v.name}-{index}</p>)
}
const handleChangeVal = (e) => {
setValue(e.target.value)
}
return (<>
<input placeholder="请输入内容..." ref={inputRef} value={val} onChange={handleChangeVal} />
<button onClick={dispatchAdd}>dispatchAdd</button>
{renderData()}
</>)
}
我们在hook/useReducer中定义了一个useReducer,useReducer(callback, initState)的第一个参数就是一个纯函数,useReducer返回的就是一个数组[state, disptch],实际上这个dispatch做了啥呢,其实就是调用了callback返回的结果,重新设值了。
// hook/useReducer.js
import { useState } from 'react'
// 自定义一个reducer
export function useReducer(reducer, initState) {
const [state, setState] = useState(initState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch]
}
所以页面上就是ok了

所以一个自定义useReucer就已经可以了,其实关于自定义hook返回的并不一定是一个[state, dispatch],这点因你你自己业务而定,但是记住一点hook就是一个纯函数
官方react已经实现了useReducer,所以如果引用官方,那就很简单了,就只需要注释引入自定义引入hook即可
import React, { useState, useEffect, createRef, useReducer } from "react";
// import { useReducer } from '../../hook/useReducer';
function addReducer(state, action) {
switch (action.type) {
case 'add':
return [...state, {
...action.payload
}];
default:
return state;
}
}
function Title() {
const [data, dispatch] = useReducer(addReducer, []);
const [val, setValue] = useState('Web技术学苑');
const inputRef = createRef();
const dispatchAdd = () => {
dispatch({
type: 'add',
payload: {
name: inputRef.current.value
}
})
}
const renderData = () => {
return data.map((v, index) => <p key={index}>{v.name}-{index}</p>)
}
const handleChangeVal = (e) => {
setValue(e.target.value)
}
return (<>
<input placeholder="请输入内容..." ref={inputRef} value={val} onChange={handleChangeVal} />
<button onClick={dispatchAdd}>dispatchAdd</button>
{renderData()}
</>)
}
useCallback
缓存内部依赖的回调函数,避免不必要渲染
import React, { useState, useEffect, createRef, useReducer, useCallback } from "react";
function Title() {
const [count, setCount] = useState(0);
const [data, dispatch] = useReducer(addReducer, []);
const [val, setValue] = useState('Web技术学苑');
const inputRef = createRef();
const add = () => {
console.log(count, 'before count1')
setCount(count + 1);
console.log(count, 'before count2')
}
...
const handleChangeVal = useCallback((e) => {
setValue(e.target.value)
}, [])
return (<>
<p>{renderText()}</p>
<button onClick={add}>添加</button>
<input placeholder="请输入内容..." ref={inputRef} value={val} onChange={handleChangeVal} />
<button onClick={dispatchAdd}>dispatchAdd</button>
{renderData()}
</>)
}
useRef
这个hook与createRef的作用类似,可以拿到具体dom,以上createRef可以换成useRef
useMemo
在渲染期间完成的,作为优化组件渲染的一种手段,useMemo返回的是一个组件,可以依赖于某个值,而做不必要的渲染
...
function Title() {
const [count, setCount] = useState(0);
const prevCount = useRef(0);
const [data, dispatch] = useReducer(addReducer, []);
const [val, setValue] = useState('Web技术学苑');
const inputRef = useRef();
const add = () => {
console.log(count, 'before count1')
setCount(count + 1);
console.log(count, 'before count2')
}
const dispatchAdd = () => {
dispatch({
type: 'add',
payload: {
name: inputRef.current.value
}
})
}
const renderText = () => {
console.log(count, 'render')
return count
}
const renderData = () => {
console.log(data)
return data.map((v, index) => <p key={index}>{v.name}-{index}</p>)
}
// const handleChangeVal = (e) => {
// setValue(e.target.value)
// }
const handleChangeVal = useCallback((e) => {
setValue(e.target.value)
}, [])
const input = useMemo(() => <input placeholder="请输入内容..." ref={inputRef} value={val} onChange={handleChangeVal} />, [val])
return (<>
<p>prev:{prevCount.current}</p>
<p>now: {renderText()}</p>
<button onClick={add}>添加</button>
{input}
<button onClick={dispatchAdd}>dispatchAdd</button>
{renderData()}
</>)
}
memo
缓存组件,相当于class组件的PureComponent,会以props做浅比较优化
import React, { useState, useEffect, useRef, useReducer, useCallback, memo, useMemo } from "react";
// import { useReducer } from '../../hook/useReducer'
...
function Title() {
const [data, dispatch] = useReducer(addReducer, []);
...
const Pom = (props) => props.data.map((v, index) => <p key={index}>{v.name}-{index}</p>)
const List = memo(() => <Pom data={data}></Pom>)
const handleChangeVal = useCallback((e) => {
setValue(e.target.value)
}, [])
const input = useMemo(() => <input placeholder="请输入内容..." ref={inputRef} value={val} onChange={handleChangeVal} />, [val])
// console.log(input, 'input')
return (<>
{/* <input placeholder="请输入内容..." ref={inputRef} value={val} onChange={handleChangeVal} /> */}
{input}
<button onClick={dispatchAdd}>dispatchAdd</button>
<List></List>
</>)
}
export default Title
在官网提供了一篇hook的FAQ非常值得一看
总结
hook替换写以前的class组件方式,hook让纯函数组件有了自己的状态
useEffect就是以前class组件componentDidMount,componentDidupdate,componentWillUnmount的集合,每次更新state,dom渲染后会调用useEffect,内部获取的state是最新更新后的值常用
hookApi的使用,useState,useEffect,useReducer,useCallback,useMemo消除
useEffect副作用,在useEffect中返回一个函数即可memo是纯函数组件的优化类似class组件的PureComponent本文示例code example




