React Hooks 学习笔记

网友投稿 769 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

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

React Hooks 学习笔记

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系统各项指标详解
相关文章