React Hooks 学习笔记

网友投稿 720 2022-05-30

文章出处: 拉 勾 大前端 高薪训练营

1. React Hooks 介绍

对函数型组件进行增强,让函数型组件可以存储状态,可以拥有处理副作用的能力,让开发者在不使用类组件的情况下,实现相同的功能。

缺少逻辑复用的机制

为了复用逻辑增加无实际渲染效果的组件,增加了组件层级,显示十分臃肿,增加了调试的难度以及运行效率的降低

类组件经常会变得很复杂难以维护

将一组相干的业务逻辑拆分到了多个生命周期函数中,在一个生命周期函数内,存在多个不相干的业务逻辑

类成员方法不能保证 this 指向的正确性

2. React Hooks 使用

Hooks 意为钩子, React Hooks 就是一堆钩子函数, React 通过这些钩子函数对函数型组件进行增强,不同的钩子函数提供了不同的功能

用于为函数组件引入状态

import {useState} from 'react' function App() { const [count, setCount] = useState(0) return (

{count}
); } export default App;

1

2

3

4

5

6

7

8

9

10

11

12

13

接受唯一的参数即状态初始值,初始值可以是任意数据类型。

const [count, setCount] = useState(0) const [person, setPerson] = useState({ name: '张三', age: 20 })

1

2

返回值为数组,数组中存储值和更改状态值的方法,方法名称约定以 set 开头,后面加上状态名称。

const [count, setCount] = useState(0)

1

方法可以被调用多次,用以保存不同状态值

1

2

3

参数可以是一个函数,函数返回什么,初始状态值就是什么,函数只会被调用一次,在初始值是动态值的情况。

// 当初始值是动态值 // 这样写每次渲染都会执行 const propsCount = props.count || 0 // const [count, setCount] = useState(propsCount) // 应该这样写 const [count, setCount] = useState(() => { return props.count || 0 // 只有第一次执行的时候才会执行 })

1

2

3

4

5

6

7

8

9

设置状态值方法的参数可以是一个值也可以是一个函数,设置状态值方法的方法本身是异步的

如果代码依赖状态值,那要写在回调函数中:

function handleCount () { setCount((count) => { const newCount = count + 1 document.title = newCount return newCount }) }

1

2

3

4

5

6

7

8

9

useReducer 是另一种让函数组件保存状态的方式,可以将 dispatch 传给子组件使用

import { useReducer } from "react"; export default function App () { function reducer (state, action) { switch (action.type) { case 'increment': return state + 1 case 'decrement': return state - 1 default: return state; } } const [count, dispatch] = useReducer(reducer, 0) return (

{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

在跨组件层级获取数据时简化获取数据的代码

import { createContext, useContext } from "react"; const countContext = createContext() export default function App () { return ( ) } function Foo () { const value = useContext(countContext) return (

I am Foo {value}
) }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

让函数型组件拥有处理副作用的能力,类似生命周期函数

useEffect 执行机制

可以把 useEffect 看做 componentDidMount, componentDidUpdate 和 componentWillUnmount 这三个函数的组合

useEffect(() => {}) => componentDidMount, componentDidUpdate

useEffect(() => {}, []) => componentDidMount

useEffect(() => () => {}) => componentDidUpdate, componentWillUnmount

useEffect(() => () => {}, []) => componentWillUnmount

import { useEffect, useState } from "react"; import ReactDOM from 'react-dom' export default function App () { const [count, setCount] = useState(0) // 组件挂载完成之后执行,组件数据更新之后执行 // useEffect(() => { // console.log('123') // }) // 组件挂载完成之后执行 // useEffect(() => { // console.log('456') // }, []) useEffect(() => { return () => { console.log('组件被卸载了') } }) return (

{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

useEffect 使用方法

(1). 为 window 对象添加滚动事件

(2). 设置定时器让 count 数值每隔一秒增加 1

import { useEffect, useState } from "react"; import ReactDOM from 'react-dom' export default function App () { const [count, setCount] = useState(0) function onScroll () { console.log('页面滚动了') } useEffect(() => { window.addEventListener('scroll', onScroll) return () => { window.removeEventListener('scroll', onScroll) } }, []) useEffect(() => { const timerId = setInterval(() => { setCount(count => { const newCount = count + 1 document.title = newCount return newCount }) }, 1000); return () => { clearTimeout(timerId) } }, []) return (

{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

36

37

useEffect 解决的问题

(1). 按照用途将代码进行分类(将一组相同的业务逻辑归置到了同一个副作用函数中)

(2). 简化重复代码,是组件内部代码更加清晰

只有指定数据发生变化时触发 effect

import { useEffect, useState } from "react"; export default function App () { const [count, setCount] = useState(0) const [person, setPerson] = useState({name: '张三'}) useEffect(() => { // person 的变化不会触发 useEffect , 因为第二个数组参数中只监听了 count document.title = count console.log(111) }, [count]) return (

{count}
) }

1

2

3

React Hooks 学习笔记

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

useEffect 钩子函数结合异步函数

useEffect 中的参数函数不能是异步函数,因为 useEffect 函数要返回清理资源的函数,如果是异步函数就变成了返回 Promise. 可以写成自执行函数的形式:

useEffect(() => { (async () => { await axios.get() })() }, [])

1

2

3

4

5

useMemo 的行为类似 Vue 中的计算属性,可以检测某个值的变化,根据变化只计算新值。

useMemo 会缓存计算结果,如果检测子没有发生变化,及时组建重新渲染,也不会重新计算,此行为可以有助于避免在每个渲染上进行昂贵的计算。

import { useState, useMemo } from "react"; export default function App () { const [count, setCount] = useState(0) const [bool, setBool] = useState(true) const result = useMemo(() => { console.log('111') // 只有 count 改变才会重新执行这个回调函数 return count * 2 }, [count]) return (

{count} {result} {bool ? '真' : '假'}
) }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

性能优化,如果本组件中的数据没有发生变化,阻止组件更新,类似类组件中的 PureComponent 和 shouldComponentUpdate

import { memo } from 'react' const Foo = memo(function Foo () { return

I am Foo
})

1

2

3

4

5

性能优化,缓存函数,使用组件重新渲染时得到相同的函数实例。否则每次父组件渲染,函数变量的实例都会变化,导致里层组件被重新渲染

import { useState, memo, useCallback } from "react"; const Foo = memo(function Foo (props) { console.log('Foo 重新渲染了') return

I am Foo
}) export default function App () { const [count, setCount] = useState(0) const resetCount = useCallback(() => { setCount(0) }, [setCount]) return (
{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

import { useRef } from "react"; export default function App () { const box = useRef() return (

) }

1

2

3

4

5

6

7

8

9

10

即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染。

import { useRef, useState, useEffect} from "react"; export default function App () { const [count, setCount] = useState(0) let timeId = useRef() // 夸组件生命周期 useEffect(() => { // 使用这个 ref 的 current 属性存储数据 timeId.current = setInterval(() => { setCount(count => count + 1) }, 1000); }, []) const stopCount = () => { console.log(timeId.current) clearInterval(timeId.current) } const box = useRef() return (

{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

3. 自定义 Hook (为了在组件之间形成逻辑共享)

自定义 Hook 是标准的封装和共享逻辑的方式

自定义 Hook 是一个函数,其名称以 use 开头

自定义 Hook 其实就是逻辑和内置 Hook 的组合

如何使用自定义 Hook:

import { useState, useEffect} from "react"; import axios from "axios"; function useGetPost () { const [post, setPost] = useState({}) useEffect(() => { axios.get('https://jsonplaceholder.typicode.com/posts/1') .then((response) => { setPost(response.data) }) }, []) return [post, setPost] } export default function App () { const [post, setPost] = useGetPost() return (

{post.title}

{post.body}
) }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

封装公共逻辑:

import { useState} from "react"; function useUpdateInput (initialState) { const [value, setValue] = useState(initialState) return { value, onChange: e => setValue(e.target.value) } } export default function App () { const usernameInput = useUpdateInput('') const passwordInput = useUpdateInput('') const submitForm = event => { event.preventDefault(); console.log(usernameInput.value) console.log(passwordInput.value) } return (

) }

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

4. React 路由 Hooks

index.js

import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router } from "react-router-dom"; import App from './App'; ReactDOM.render( , document.getElementById('root') );

1

2

3

4

5

6

7

8

9

10

11

App.js

import { Link, Route } from "react-router-dom"; import Home from './pages/Home' import List from './pages/List' export default function App () { return ( <>

首页 列表页
) }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

Home.js

import { useHistory, useLocation, useRouteMatch, useParams } from "react-router-dom"; export default function Home(props) { console.log(props) console.log(useHistory()) console.log(useLocation()) console.log(useRouteMatch()) console.log(useParams()) return

Home works

; }

1

2

3

4

5

6

7

8

9

10

11

12

输出结果:

{history: {…}, location: {…}, match: {…}, staticContext: undefined}

{length: 7, action: “PUSH”, location: {…}, createHref: ƒ, push: ƒ, …}

{pathname: “/home/zhangsan”, search: “”, hash: “”, state: undefined, key: “o6w5y3”}

{path: “/home/:name”, url: “/home/zhangsan”, isExact: true, params: {…}}

{name: “zhangsan”}

List.js

export default function List(props) { console.log(props) return

List works

; }

1

2

3

4

5

6

使用数组 state 存储状态,用数组 setters 存储 setState方法,利用闭包管理,将 下标 stateIndex 缓存在闭包中,创建 对应的 setState.

// import { useState } from 'react' import ReactDOM from 'react-dom' let state = [] let setters = [] let stateIndex = 0 function createSetter (index) { return function (newState) { state[index] = newState render() } } function useState (initialState) { state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState setters.push(createSetter(stateIndex)) let value = state[stateIndex] let setter = setters[stateIndex] stateIndex ++ return [value, setter] } function render () { stateIndex = 0 ReactDOM.render( , document.getElementById('root') ) } export default function App () { const [count, setCount] = useState(0) const [name, setName] = useState('张三') return

{count} {name}
}

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

36

37

38

39

40

// import { useState } from 'react' import ReactDOM from 'react-dom' let state = [] let setters = [] let stateIndex = 0 function createSetter (index) { return function (newState) { state[index] = newState render() } } function useState (initialState) { state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState setters.push(createSetter(stateIndex)) let value = state[stateIndex] let setter = setters[stateIndex] stateIndex ++ return [value, setter] } function render () { stateIndex = 0 effectIndex = 0 ReactDOM.render( , document.getElementById('root') ) } // 上一次的依赖值 let prevDepsAry = [] let effectIndex = 0 /** * useEffect * @param {function} callback 回调函数 * @param {Array} depsAry 依赖数组 * @returns {function} 清理函数 */ function useEffect (callback, depsAry) { if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect 第一个参数必须是一个函数') if (typeof depsAry === 'undefined') { // 没有传递 callback() } else { // 判断 depsAry 是不是数组 if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect 第二个参数必须是一个数组') // 获取上一次的状态值 let prevDeps = prevDepsAry[effectIndex] // 将当前的依赖值和上一次的依赖值作对比,如果有变化,调用 callback let hasChanged = prevDeps ? !depsAry.every((dep, index) => dep === prevDeps[index]) : true // 判断值是否有变化 if (hasChanged) { callback() } // 同步依赖值 prevDepsAry[effectIndex++] = depsAry } } export default function App () { const [count, setCount] = useState(0) const [name, setName] = useState('张三') useEffect(() => { console.log('Hello') }, [count]) useEffect(() => { console.log('World') }, [name]) // 测试不传监听数据的情况 useEffect(() => { console.log('xxx') }, []) return

{count} {name}
}

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

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

// import { useState } from 'react' // import { useReducer } from 'react' import ReactDOM from 'react-dom' let state = [] let setters = [] let stateIndex = 0 function createSetter (index) { return function (newState) { state[index] = newState render() } } function useState (initialState) { state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState setters.push(createSetter(stateIndex)) let value = state[stateIndex] let setter = setters[stateIndex] stateIndex ++ return [value, setter] } function render () { stateIndex = 0 effectIndex = 0 ReactDOM.render( , document.getElementById('root') ) } // 上一次的依赖值 let prevDepsAry = [] let effectIndex = 0 /** * useEffect * @param {function} callback 回调函数 * @param {Array} depsAry 依赖数组 * @returns {function} 清理函数 */ function useEffect (callback, depsAry) { if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect 第一个参数必须是一个函数') if (typeof depsAry === 'undefined') { // 没有传递 callback() } else { // 判断 depsAry 是不是数组 if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect 第二个参数必须是一个数组') // 获取上一次的状态值 let prevDeps = prevDepsAry[effectIndex] // 将当前的依赖值和上一次的依赖值作对比,如果有变化,调用 callback let hasChanged = prevDeps ? !depsAry.every((dep, index) => dep === prevDeps[index]) : true // 判断值是否有变化 if (hasChanged) { callback() } // 同步依赖值 prevDepsAry[effectIndex++] = depsAry } } function useReducer (reducer, initialState) { const [state, setState] = useState(initialState) function dispatch (action) { const newState = reducer(state, action) setState(newState) } return [state, dispatch] } export default function App () { function reducer(state, action) { switch (action.type) { case 'increment': return state + 1 case 'decrement': return state - 1 default: return state } } const [cnt, dispatch] = useReducer(reducer, 0) return

{cnt}
}

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

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

React 数据结构

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Linux 操作系统原理 — 内存 — 大页内存
下一篇:LoadRunner监控window系统各项指标详解
相关文章