react的hooks教程

这是一篇react的hooks教程

另外欢迎大家访问我的博客

react hooksclass Component 的区别

  • 写法更加简洁,不再需要写冗长的生命周期函数
  • class Componet hoc 的阅读让人看起来不易理解, 在组件之间复用状态逻辑很难.

hooks 使用规则

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在React的函数组件中调用Hook。不要在其他JavaScript函数中调用。除了自定义的hook以外

react hooks 常见的api使用

useState 用法

identity: const [state, setState] = useState(initialState)

useState的作用是在react函数组件中添加state的hook。

1
2
3
4
5
6
7
8
9
10
import React, { useState } from 'react';
function Count() {
/*
- count <=> this.state.count, setCount <=> this.setState
- setCount 支持两种写法 setCount(count + 1) or setCount(preState => preState + 1)
第一种写法是一种异步机制,会将短时间内的多个setCount合并成一个方法,第二种写法是为了不使用第一种的合并机制。
*/
const [count, setCount] = useState(0);
return <div onClick={setCount(pre => pre + 1)}>{count}</div>
}

useEffect 用法

identity: useEffect(callBack:clearCallBack, [deps])

useEffect的作用是在函数组件中执行副作用操作, 等价于在ComponetDidMount,ComponentDidUpdate, ComponentWillUnmount 三个函数的组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { useState, useEffect } from 'react';
function Example() {
/*
- useEffect接受一个callBack参数和数组参数
- 数组中的值作为依赖,数组中的值发生变化的时候,callBack会重新调用。等价于ComponentDidUpdate
- callBack可以return一个clearCallBack,在组件卸载的时候调用clearCallBack。等价于ComponentWillUnmount
- useEffect默认会在render流程执行完以后,在调用callBack。等价于ComponetDidMount
*/
const [isonline, setIsOnline] = useState(false);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange)
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
}
}, [isonline])
}

useContext 用法

useContext的作用是接受一个context对象,并返回该context的当前值。主要用于深层级的组件通讯。需要和React.createContext配合使用
context的值由上层组件中距离当前组件最近的 <MyContext.Provider>的 value prop决定。另外value的值更新会引起调用了uesContext的组件重新渲染。

注意调用useContext的组件即使用了React.memo进行声明,也会重新渲染。因此需要使用memoization来进行优化.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { createContext, Children, useContext, useMemo } from 'react'
const GlobalContext = React.createContext()

function Child() {
const data = useContext(GlobalContext)
// memoization 写法
return useMemo(() => {
return <div>{data.name}</div>
}, [data.name])
}

function Parent() {
return (
<div>
<p>Parent</p>
<GlobalContext.Provider value={{name: 'woyao'}}>
<Child />
</GlobalContext.Provider>
</div>
)
}

useRef 用法

useRef返回一个可变的ref对象,ref.current在组件内是一个全局常量。相当于在组件外写了一个全局常量。也就是说每次重新渲染函数组件时,返回的ref对象都是同一个。 常用于访问Dom,当做全局变量

访问 Dom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { useRef } from 'react'
function Example() {
const inputElement = useRef(null)
const btnClick = (event) => {
inputElement.current.focus()
}
return (
<React.Fragment>
<input ref={inputElement} />
<button ref={btn} onClick={btnClick}/>
</React.Fragment>
)
}

当做全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, { useRef, useEffect, useState } from 'react'
function usePrevious(value) {
// 每次重新渲染,都会执行useRef,
const ref = useRef()
/*
- 不设置依赖,每次reRender都会重新执行
- 能够返回上一次渲染之前value是什么值
- 注意是先执行return, 在执行useEffect
*/
useEffect(() => {
ref.current = value
})
return ref.current
}
function Counter() {
const [count, setCount] = useState(0)
const preCount = usePrevious(count)
return (
<div>
<p>previous: {preCount} </p>
<p>now: {count}</p>
<button onClick={() => setCount(count + 1)}>click</button>
</div>
)
}

useImperativeHandle 用法

identity: useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle可以让你在使用ref时自定义暴露给父组件的实例值, 与forwardRef一起使用. 让你能够父组件调用子组件的方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React, { useRef, useImperativeHandle, forwardRef } from 'react'
function MyInput(props, ref) {
const inputRef = useRef()
const childFunc = () => {
console.log('hh')
}
/*
ref: ref实例
createHandle: 给ref实例绑上方法
[dps]: 当deps发生变化的时候, createHandle 重新执行
*/
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
},
childFunc
}))
return <input ref={inputRef} />
}

MyInput = forwardRef(MyInput)

function App() {
const myInputCoponent = useRef()

return (
<>
<MyInput ref={myInputCoponent} />
<button onClick={() => { myInputCoponent.current.childFunc() }}> focus now </button>
</>
)
}

useReducer 用法

identity: const [state, dispatch] = useReducer(reducer, initialArg, init)

useReducer是useState的替代方案,接受一个形如(state, action) => newState 的 reducer, 并返回
当前的state以及其配套的dispatch方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const initialState = {count: 0};

function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}

useMemo 用法

identity: const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
newRenderTemplate = useMemo(() => renderTemplate(), [deps])

useMemo是减少组件渲染次数,优化组件性能的hook, 传入useMemo的函数会在渲染期间执行,请不要再函数
内部执行与渲染无关的操作,其实也就是只有依赖项发生变化才会生成新的memoizedValue。这样就减少了不必要的
渲染。一般用在组件中进行解耦操作,与这个逻辑渲染相关的逻辑发生变化就重新渲染,而不相关的就不会重新渲染。
大白话就是有一个count逻辑相关的渲染,还有一个和name相关的逻辑渲染。不要因为name的state属性变化导致
count的渲染函数也重新执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { useState, useMemo } from 'react'
const log = console.log.bind(console)

function Child(props) {
log('child render')
const [count, SetCount] = useState(0)
const renderCountTemplate = (count) => {
console.log('count render')
return <div>{count}</div>
}
return (
<div>
child, {props.name}
{/*
- 使用useMemo防止了不必要的渲染更新,不会因为与当前父组件的props发生变化就会重新对renderCountTemplate进行执行。
*/}
{ useMemo(() => renderCountTemplate(count), [count])}
</div>
)
}
// Child组件优化:组件内 prop,state 的值发生变化才会重新渲染。防止了父组件的更新,子组件也进行不必要的更新
Child = React.memo(Child)


function App() {
log('parent render')
const [name, SetName] = useState('')
return (
<>
<div>parent, {name}</div>
<input onChange={(event) => SetName(event.target.value)} />
<Child name={name} />
</>
)
}

useCallback 用法

identity: const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);

useCallback是减少组件渲染次数,优化组件性能的hook, 与useMemo类似。回调函数仅在某个依赖项改变时才会更新
常用在对事件函数中匿名函数的处理。当然能用到useMemo的地方,useCallback也可以用到。

1
2
3
4
5
6
7
8
9
10
import React, { useCallback, useState } from 'react'

const [ count, setCount ] = useState(0)
function Counter() {
<!--
- 防止了每次count发生变化,导致的重新渲染,都需要重新生成一个新的匿名函数
- 也就是说 () => {setCount(count + 1)} 这个匿名函数不需要再从新的内存空间中创建
-->
return <div onClick={useCallback(() => setCount(count+1), [])}>{count}</div>
}

useLayoutEffect 用法

useLayoutEffect 官方解释说它会在所有的DOM变更之后同步调用effect,可能useEffect是在所有的DOM变更之后异步调用effect吧.

  • 可以使用它来读取 DOM 布局并同步触发重渲染
  • 在浏览器执行绘制之前,useLayoutEffect内部的更新计划将被同步刷新。
  • 这么说吧,我也不太useLayoutEffect的区别。有下面一段代码可以参考一下。估计在使用useEffect的时候带来了页面的抖动问题的时候就使用useLayoutEffect。网上的解释:layout会在组件树构建完毕或者刷新完毕后同步立刻执行。effect会等其他js代码执行完毕后执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function App() {
    const [count, setCount] = useState(0);

    useEffect(() => {
    if (count === 0) {
    const randomNum = 10 + Math.random()*200
    setCount(10 + Math.random()*200);
    }
    }, [count]);

    return (
    <div onClick={() => setCount(0)}>{count}</div>
    );
    }

    效果如下:
    结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function App() {
    const [count, setCount] = useState(0);

    useLayoutEffect(() => {
    if (count === 0) {
    const randomNum = 10 + Math.random()*200
    setCount(10 + Math.random()*200);
    }
    }, [count]);

    return (
    <div onClick={() => setCount(0)}>{count}</div>
    );
    }

    效果如下:
    结果

自定义hook

其实也就是hook mixin, 公共的方法逻辑可以写成自定义hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 一个ComponentDidUpdate的简易实现
import { useEffect, useRef } from 'react'

function useDidUpdate(cb, deps=[]) {
const didMount = useRef(false)

useEffect(() => {
if (!didMount.current) {
didMount.current = true
return
}
cb()
}, deps)

return didMount.current
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 一个redux的简易实现
import React, { useContext, useReducer } from 'react'
const initState = { count: 0 }
const Store = React.createContext(initStore)
const MapActionReducer = {
['ADD'](state, action) {
return {...state, count: action.payload}
}
}

const reducer = (initState, action) => {
return MapActionReducer[action.type](initState, action)
}

function useGlobalReduxHook(Component) {
// dispatch触发state发生变化,会重新执行渲染
const [state, dispatch] = useReducer(reducer, Store)
return (
<Store.Provider value={state, dispatch} />
<Component />
</Store.Provider>
)
}
文章作者: woyao
文章链接: https://chenwoyao.github.io/2021/04/16/前端笔记/react系列/react的hooks教程/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 woyao的博客