React Hooks 学习笔记
React Hooks 学习笔记
文章出处: 拉 勾 大前端 高薪训练营
1. React Hooks 介绍
1.1 React Hooks 是用来做什么的
对函数型组件进行增强,让函数型组件可以存储状态,可以拥有处理副作用的能力,让开发者在不使用类组件的情况下,实现相同的功能。
1.2 类组件的不足(Hooks 要解决的问题)
- 缺少逻辑复用的机制
为了复用逻辑增加无实际渲染效果的组件,增加了组件层级,显示十分臃肿,增加了调试的难度以及运行效率的降低
- 类组件经常会变得很复杂难以维护
将一组相干的业务逻辑拆分到了多个生命周期函数中,在一个生命周期函数内,存在多个不相干的业务逻辑
- 类成员方法不能保证 this 指向的正确性
2. React Hooks 使用
Hooks 意为钩子, React Hooks 就是一堆钩子函数, React 通过这些钩子函数对函数型组件进行增强,不同的钩子函数提供了不同的功能
2.1 useState()
用于为函数组件引入状态
import {useState} from 'react'
function App() {
const [count, setCount] = useState(0)
return (
<div>
<span>{count}</span>
<button onClick={()=>setCount(count+1)}> + 1</button>
</div>
);
}
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
- 方法可以被调用多次,用以保存不同状态值
<button onClick={()=>setCount(count+1)}> + 1</button>
<button onClick={()=>setPerson({name: '李四', age: 30})}>setPerson</button>
<button onClick={()=>setPerson({...person, name: '李四'})}>setPerson(只改变一个属性,其他属性不变)</button>
- 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
})
}
<button onClick={handleCount}> + 1</button>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.2 useReducer()
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 (
<div>
<button onClick={() => dispatch({type: 'decrement'})}>-1</button>
<span>{count}</span>
<button onClick={() => dispatch({type: 'increment'})}>+1</button>
</div>
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
2.3 useContext()
在跨组件层级获取数据时简化获取数据的代码
import { createContext, useContext } from "react";
const countContext = createContext()
export default function App () {
return (
<countContext.Provider value={1000}>
<Foo />
</countContext.Provider>
)
}
function Foo () {
const value = useContext(countContext)
return (
<div>I am Foo {value}</div>
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
2.4 useEffect()
让函数型组件拥有处理副作用的能力,类似生命周期函数
- 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 (
<div>
<span>{count}</span>
<button onClick={() => setCount(count+1)}>+1</button>
<button onClick={() => ReactDOM.unmountComponentAtNode(document.getElementById('root'))}>卸载组件</button>
</div>
)
}
- 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 (
<div>
<span>{count}</span>
<button onClick={() => { ReactDOM.unmountComponentAtNode(document.getElementById('root')) }} >卸载组件</button>
</div>
)
}
- 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 (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}> + 1</button>
<button onClick={() => setPerson({name: '李四'})}>setPerson</button>
</div>
)
}
- 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
2.5 useMemo()
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 (
<div>
<span>{count} {result}</span>
<span>{bool ? '真' : '假'}</span>
<button onClick={() => setCount(count + 1)}> + 1</button>
<button onClick={() => setBool(!bool)}>setBool</button>
</div>
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
2.6 memo 方法
性能优化,如果本组件中的数据没有发生变化,阻止组件更新,类似类组件中的 PureComponent 和 shouldComponentUpdate
import { memo } from 'react'
const Foo = memo(function Foo () {
return <div>I am Foo</div>
})
- 1
- 2
- 3
- 4
- 5
2.7 useCallback()
性能优化,缓存函数,使用组件重新渲染时得到相同的函数实例。否则每次父组件渲染,函数变量的实例都会变化,导致里层组件被重新渲染
import { useState, memo, useCallback } from "react";
const Foo = memo(function Foo (props) {
console.log('Foo 重新渲染了')
return <div>
<span>I am Foo</span>
<button onClick={props.resetCount}>resetCount</button>
</div>
})
export default function App () {
const [count, setCount] = useState(0)
const resetCount = useCallback(() => {
setCount(0)
}, [setCount])
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}> + 1</button>
<Foo resetCount={resetCount} />
</div>
)
}
- 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
2.8 useRef()
2.8.1 获取DOM元素对象
import { useRef } from "react";
export default function App () {
const box = useRef()
return (
<div ref={box}>
<button onClick={() => console.log(box)}> DIV </button>
</div>
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2.8.2 保存数据(跨组件周期)
即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染。
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 (
<div ref={box}>
<span>{count}</span>
<button onClick={() => stopCount()}> 停止 </button>
</div>
)
}
- 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 (
<div>
<h1>{post.title}</h1>
<div>{post.body}</div>
</div>
)
}
- 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 (
<form onSubmit={submitForm}>
<input type="text" name="username" {...usernameInput} />
<input type="password" name="password" {...passwordInput} />
<input type="submit" />
</form>
)
}
- 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
4.1 react-router-dom 路由提供的钩子函数
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(
<Router>
<App />
</Router>,
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 (
<>
<div>
<Link to="/home/zhangsan">首页</Link>
<Link to="/list">列表页</Link>
</div>
<div>
<Route path="/home/:name" component={Home} />
<Route path="/list" component={List} />
</div>
</>
)
}
- 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 <div>
<h1>Home works</h1>
</div>;
}
- 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 <div>
<h1>List works</h1>
</div>;
}
- 1
- 2
- 3
- 4
- 5
- 6
5. useState 钩子函数的实现原理
使用数组 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(
<App/>,
document.getElementById('root')
)
}
export default function App () {
const [count, setCount] = useState(0)
const [name, setName] = useState('张三')
return <div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}> + 1</button>
<span>{name}</span>
<button onClick={() => setName('李四')}> 李四</button>
</div>
}
- 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
6. useEffect 钩子函数的实现原理
// 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(
<App/>,
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 <div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}> + 1</button>
<span>{name}</span>
<button onClick={() => setName('李四')}> 李四</button>
</div>
}
- 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
7. useReducer 钩子函数的实现原理
// 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(
<App/>,
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 <div>
<div>
<button onClick={() => dispatch({type: 'increment'})}> + 1</button>
<span>{cnt}</span>
<button onClick={() => dispatch({type: 'decrement'})}> - 1</button>
</div>
</div>
}
- 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
文章来源: blog.csdn.net,作者:爱玲姐姐,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/jal517486222/article/details/112257091
- 点赞
- 收藏
- 关注作者
评论(0)