day-05
Hooks
Hooks:钩子、钩住,是 React@16.8 提供的新功能。
Hooks作用: 能为函数式组件提供状态和生命周期,Hooks 只能在函数组件中使用
Hooks(提供状态和生命周期)+函数组件(展示内容)
Hooks解决了什么问题
-
组件的状态逻辑复用问题 a,在 Hooks 之前,组件的状态逻辑复用经历了:mixins(混入)、HOC(高阶组件)、render props 等模式。 b,(早已废弃)mixins 的问题:数据来源不清晰;命名冲突。 c,HOC、render props 的问题:重构组件结构,导致组件形成 JSX 嵌套地狱问题。 -
class组件自身的问题 a,选择:函数组件和 class 组件之间的区别以及使用哪种组件更合适。 b,需要理解 class 中的 this 是如何工作的。 c,同一业务的状态和业务逻辑被拆分到不同位置。 -
相比于函数组件来说,类组件不利于代码压缩和优化,也不利于TS的类型推导 # 例如不能把 componentDidMount 压缩成 c
# 例如写 this 的时候没有提示,因为 this 只有在调用的时候才能确定指向,编写代码期间 TS 是不知道的
Hooks 渐进策略
目标
能够了解 Hooks 和之前 class 的写法是可以共存的。
内容
-
官方没有计划从 React 中移除 class 组件。 -
Hooks 和现有代码可以同时工作,建议渐进式地使用它们。 a,不推荐:大规模使用 Hooks 直接重构现有组件。 b,推荐:新功能用 Hooks,Hooks 实现不了的复杂功能,也可以继续用 class。 c,具体操作,从一些功能简单、非核心功能的组件开始使用 Hooks。 -
不能在 Hooks 组件中,使用 class 组件相关的 API。 a,state 与 setState。 b,钩子函数,componentDidMount 、componentDidUpdate 、componentWillUnmount 。 c,this 相关的用法。 -
原来学习的绝大部分知识点还是要用的。 a,JSX:{} 、onClick={handleClick} 、条件渲染、列表渲染、样式处理等。 b,组件:函数组件、组件通讯。 c,React 开发理念:单向数据流 、状态提升 等。 d,解决问题的思路、技巧、常见错误的分析等。
useState 使用
作用:
为函数组件提供状态和修改状态的方法。
使用:
- 导入
useState 函数。 - 调用
useState 函数,并传入状态的初始值。 - 从
useState 函数的返回值中,拿到状态和修改状态的函数。
import React, { useState } from 'react'
export default function App() {
const [count, setCount] = useState(0)
return (
<div>
<h2>计数器:{count}</h2>
<button
onClick={() => {
setCount(count + 1)
}}
>
+1
</button>
</div>
)
}
细节
- 参数:初始状态,比如传入 0 就表示该状态的初始值为 0。
- 注意:此处的状态可以是任意值(比如,数值、字符串、对象等),注意 class 组件中的 state 必须是对象。
- 返回值:数组,数组里面包含两个值,状态和修改该状态的方法。
- 约定:修改状态的方法以 set 开头,后面跟上状态的名称。
注意事项
-
读取状态 目前,useState 只能在函数组件内部调用(也可以在后续学习的自定义 Hook 中使用),所以返回的状态也是函数内部的局部变量。 -
修改状态 a,setCount(newValue) 是一个函数调用,参数表示新的状态值。 b,调用该函数后,将使用新的状态值直接替换旧状态。 c,修改状态后,组件会自动重新渲染。 -
状态的不可变性(修改状态的时候,要使用新的状态替换掉旧的状态,而不要直接修改原状态)。
import React, { useState } from 'react'
const App = () => {
const [obj, setObj] = useState({
count: 0,
})
const handleClick = () => {
obj.count++
setObj(obj)
}
return (
<div>
<p>{obj.count}</p>
<button onClick={handleClick}>click</button>
</div>
)
}
export default App
useState 与组件更新过程
目标
能够说出使用功能 useState() 之后,组件的更新过程。
内容
- 组件第 1 次渲染
- 调用函数式组件,从头开始执行组件中的代码逻辑。
- 调用
useState(0) 将传入的参数作为初始状态值,即:0。 - 开始渲染组件,此时得到的状态 count 值为:0。
- 组件第 2 次渲染
- 点击按钮,调用
setCount(count + 1) 来修改状态,因为状态发生改变,所以,该组件会重新渲染。 - 组件重新渲染时,会再次执行该组件中的代码逻辑。
- 再次调用
useState(0) ,此时 React 内部会拿到最新的状态值而非初始值,比如该案例中的最新状态值为 1。 - 再次渲染组件,此时,获取到的状态 count 值为:1。
- 注意:useState 的初始值(参数)只会在组件第一次渲染时生效,也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件内部会记住每次更新后的最新状态值!
useState 另一种写法
目标
掌握 useState(回调函数) 的写法。
内容
useState(回调函数) ,回调函数的返回值就是状态的初始值,该回调函数只会触发一次。
useState(() => {
return 初始值
})
import React, { useState } from 'react'
export default function App() {
console.log(1)
const [count, setCount] = useState(() => {
let defaultCount = 0
console.log(2)
for (let i = 1; i < 10000; i++) {
defaultCount += i
}
return defaultCount
})
return (
<div>
<h2>计数器:{count}</h2>
<button
onClick={() => {
setCount(count + 1)
}}
>
+1
</button>
</div>
)
}
useState 使用细则
目标
掌握如何为函数组件提供多个状态以及注意点。
内容
-
如何为函数组件提供多个状态? 多次调用 useState 即可,每一次调用返回的 [state, setState] 之间,互不影响。 -
useState 的使用细则。 a,目前,只能直接出现在函数组件中(其他 Hook 也一样)。 b,不能嵌套在 if/for/其他函数 中!(if 的条件判断、for 循环的次数、函数的调用与否都可能会影响 hook 的顺序)。 c,React 是按照 Hooks 的调用顺序来识别每一个 Hook,如果每次调用的顺序不同,导致 React 无法知道是哪一个状态和修改状态的方法。 d,可以通过开发者工具进行查看 React 对 Hook 的管理。
import React, { useState } from 'react'
export default function App() {
const [num1, setNum1] = useState(0)
const [num2, setNum2] = useState(3)
const [num3, setNum3] = useState(6)
return (
<div>
<div>
num1: {num1}
<button onClick={() => setNum1(num1 + 1)}>修改 num1</button>
</div>
<div>
num2: {num2}
<button onClick={() => setNum2(num2 + 1)}>修改 num1</button>
</div>
<div>
num3: {num3}
<button onClick={() => setNum3(num3 + 1)}>修改 num1</button>
</div>
</div>
)
}
useEffect 副作用介绍
目标
能够说出什么是副作用(side effect)。
内容
-
类比,对于 999 感冒灵来说。 a,主作用:用于治疗感冒引起的头痛,发热,鼻塞,流涕,咽痛等。 b,副作用:可见困倦、嗜睡、口渴、虚弱感。 -
那组件或一般函数的副作用是什么呢? a,组件的副作用:对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用,比如手动修改 DOM、数据(AJAX)请求、localStorage 操作等。 b,函数的副作用:如果一个函数修改了其局部环境之外的数据,那么它就被称为有副作用。 -
关于 useEffect。 作用:当你想要在函数组件中处理副作用(side effect),就要使用 useEffect 了。
总结
- 对于 React 组件来说,“主作用”是什么?
- 常见的有哪些“副作用”?
useEffect 基本使用
目标
能够在函数组件中操作 DOM(处理副作用)。
内容
- 执行时机:初始化时和数据变化的时候执行。
- 相当于 class 中的 componentDidMount + componentDidUpdate。
需求
在实际开发中,副作用是不可避免的。
📝 需求:点击按钮,让数字加 1,并把变化后的数字展示在网页标题上。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0tXb4VGg-1639142190748)(react.assets/ifer_useEffect.gif)]
步骤
- 初始化时渲染数据到网页标题上。
- 当数据变化的时候把变化后的数据渲染到网页标题上。
代码
import React, { useState, useEffect } from 'react'
const App = () => {
const [count, setCount] = useState(() => 0)
useEffect(() => {
document.title = count
})
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>click</button>
</div>
)
}
export default App
小结
上面 useEffect 的写法相当于 class 组件中哪两个钩子?
useEffect 依赖说明
目标
能够设置 useEffect 的依赖,只在 count 变化时,才执行相应的 effect。
内容
- 问题:如果组件中还有其他状态,其他状态更新时,刚刚的 effect 回调(修改标题的操作)也会执行。
- 默认:函数中的任何状态发生更新,useEffect 的回调函数都会执行。
- 优化:如何跳过不必要的执行,只有在 count 变化时,才执行相应的 effect。
- 操作:第二个参数可以传一个数组,表示只有当数组中的选项/依赖项改变时,才会重新执行该 effect。
问题
import React, { useState, useEffect } from 'react'
const App = () => {
const [count, setCount] = useState(0)
const [money, setMoney] = useState(100)
useEffect(() => {
console.log('执行了 useEffect ~~~')
document.title = count
})
return (
<div>
<p>count: {count}</p>
<p>money: {money}</p>
<button onClick={() => setCount(count + 1)}>update count</button>
<button onClick={() => setMoney(money + 1)}>update money</button>
</div>
)
}
export default App
解决
useEffect(() => {
console.log('执行了 useEffect ~~~')
document.title = count
}, [count])
小结
useEffect 的第二个参数用来干嘛的?
指定依赖项
useEffect 依赖是一个空数组
目标
通过 useEffect 如何让组件只有在第一次渲染后会执行。
内容
useEffect 的第二个参数,还可以是一个空数组([]),表示只有在组件第一次渲染后执行,一般会进行事件绑定、发送请求等。
代码
useEffect(() => {
const handleResize = () => {}
window.addEventListener('resize', handleResize)
}, [])
解释
- 仅相当于 class 组件的 componentDidMount 钩子函数的作用。
- 和 useState 一样,一个组件中也可以调用多次 useEffect。
- 推荐:一个 useEffect 只处理一个功能,有多个功能时,使用多次 useEffect。
小结
useEffect 的第二个参数是一个空数组,相当于 class 组件中的那个钩子?
useEffect 不要对依赖项撒谎
目标
能够理解如果不正确使用依赖项可能会带来的问题。
内容
- useEffect 回调函数中用到的数据(比如前面学习的 count)就是依赖数据,就应该出现在依赖项数组中。
- 如果 useEffect 回调函数中用到了某个数据,但是没有出现在依赖项数组中,就会导致一些“Bug”出现(例如 useEffect 回调不会执行)!
- 所以,不要对 useEffect 的依赖撒谎,参考 useEffect 完全指南。
import React, { useState, useEffect } from 'react'
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
// React Hook useEffect has a missing dependency: 'count'. Either include it or remove the dependency array
document.title = count
}, [])
return (
<div>
<p>count: {count}</p>
<button onClick={() => setCount(count + 1)}>update count</button>
</div>
)
}
export default App
小结
能够说出对 useEffect 第 2 个参数几种写法所代表的含义,例如不写第二个参数、写空数组、或者 [count] 分别表示什么意思?
useEffect(() => {})
useEffect(() => {}, [])
useEffect(() => {}, [count])
倒计时
避免闭包产生
import React, { useState } from 'react'
let timer = null
export default function App() {
const [count, setCount] = useState(10)
const startTime = () => {
clearInterval(timer)
timer = setInterval(() => {
setCount((count) => count - 1)
}, 1000)
}
return (
<div>
<h3>{count}</h3>
<button onClick={startTime}>开启定时器</button>
</div>
)
}
|