highlight: vs2015
本文正在参加「金石计划 . 瓜分6万现金大奖」
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
暂无评论内容