react
在16.8版本就用了hook
,hook
是在以前的无状态纯函数组件增加了内部自己的状态,而且我们完全可以用hook方式替换原有的class
组件,本文是一篇重读react官网hook
的笔记。
在开始本文之间,主要会从以下方面理解hook
为什么会有hook,hook到底有什么好
hook组件比class组件更简单,更容易维护
核心
hook
API的使用
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
是最新更新后的值常用
hook
Api的使用,useState
,useEffect
,useReducer
,useCallback
,useMemo
消除
useEffect
副作用,在useEffect
中返回一个函数即可memo
是纯函数组件的优化类似class组件的PureComponent
本文示例code example